HP ALM Rest API QCSession 411 Authentication - java

I am using HP-ALM 12.01 which seems to be stock full of issues. I cannot update to another version at this time.
I am trying to get access to the rest api to upload test results automatically from JUnit. I am using the infrastructure shown here (example application -> Infrastructure). From which, my connection scripts passes base64 encoded login info to authentication-point/authenticate and I am retrieving a valid LWSSO cookie. However, when I use this cookie to connect to rest/site-session to receive my QCSession cookies, I am receiving a 411 Length Required error. I have attempted to hard code the Content-Length into the headers as shown here
public void GetQCSession(){
String qcsessionurl = con.buildUrl("rest/site-session");
Map<String, String> requestHeaders = new HashMap<String, String>();
requestHeaders.put("Content-Type", "application/xml");
requestHeaders.put("Accept", "application/xml");
requestHeaders.put("Content-Length", "0");
try {
Response resp = con.httpPost(qcsessionurl, null, requestHeaders);
con.updateCookies(resp);
System.out.println(resp.getStatusCode());
} catch (Exception e) {
e.printStackTrace();
}
}
This did not work. I have also tried modifying the infrastructure to automatically inject the Content-Length header, as shown here
private void prepareHttpRequest(
HttpURLConnection con,
Map<String, String> headers,
byte[] bytes,
String cookieString) throws IOException {
String contentType = null;
//attach cookie information if such exists
if ((cookieString != null) && !cookieString.isEmpty()) {
con.setRequestProperty("Cookie", cookieString);
}
//send data from headers
if (headers != null) {
//Skip the content-type header - should only be sent
//if you actually have any content to send. see below.
contentType = headers.remove("Content-Type");
Iterator<Entry<String, String>>
headersIterator = headers.entrySet().iterator();
while (headersIterator.hasNext()) {
Entry<String, String> header = headersIterator.next();
con.setRequestProperty(header.getKey(), header.getValue());
}
}
// If there's data to attach to the request, it's handled here.
// Note that if data exists, we take into account previously removed
// content-type.
if ((bytes != null) && (bytes.length > 0)) {
con.setDoOutput(true);
//warning: if you add content-type header then you MUST send
// information or receive error.
//so only do so if you're writing information...
if (contentType != null) {
con.setRequestProperty("Content-Type", contentType);
}
OutputStream out = con.getOutputStream();
out.write(bytes);
out.flush();
out.close();
con.setRequestProperty("Content-Length", Integer.toString(bytes.length));
} else {
con.setRequestProperty("Content-Length", "0");
}
}
which also does not work.
note that setRequestProperty simply does a .set(key, value) to a MessageHeader
Has anyone dealt with this issue before or know how to resolve it?
Note that none of these issues occurs with postman. All 4 cookies are generated after a site-session post.

The code Example from Barney was slightly expanded since it was not adapted for ALM 12.5 setting.
Main difference is, that there are more cookies and cookies are attached to header
Config config = new Config(dataService);
String almURL = "https://" + config.host() + "/qcbin";
client = ClientBuilder.newBuilder().build();
target = client.target(almURL).path("api/authentication/sign-in");
invocationBuilder = target
.request(new String[] {"application/xml"})
.accept(new String[] {"application/xml"});
invocationBuilder.header("Authorization", getEncodedAuthString(config.username(), config.password()));
res = invocationBuilder.post(null);
String qcsessioncookie = res.getCookies().get("QCSession").getValue();
String almusercookie = res.getCookies().get("ALM_USER").getValue();
String xsrftokencookie = res.getCookies().get("XSRF-TOKEN").getValue();
String lswoocookie = res.getCookies().get("LWSSO_COOKIE_KEY").getValue();
/* Get the test-Set Data defect */
String midPoint = "rest/domains/" + config.domain() + "/projects/" + config.project();
target = client.target(almURL).path(midPoint).path("test-sets/1");
invocationBuilder = target
.request(new String[] {"application/xml"})
.accept(new String[] {"application/xml"});
concatenatedHeaderCookieString = "QCSession=" + qcsessioncookie + ";" + "ALM_USER=" + ";" + almusercookie + ";" + "XSRF-TOKEN=" + xsrftokencookie + ";"
+ "LWSSO_COOKIE_KEY=" + lswoocookie;
invocationBuilder.header("Cookie", concatenatedHeaderCookieString);
res = invocationBuilder.get();

The issue is that Java's HttpURLConnection ignores certain properties when manually set. One of these is Content-Length. This is because it automatically sets it itself. However, if you're not sending any data it simply doesn't send it, which ALM is not accepting due its outdated http protocols, as it expects to receive a Content-Length of 0.
To work around this you have to tell java to allow restrticted headers. This is done by running
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
for more information, look here Why does Content-Length HTTP header field use a value other than the one given in Java code?

POM.xml Dependency
<dependency>
<groupId>org.glassfish.jersey.bundles</groupId>
<artifactId>jaxrs-ri</artifactId>
<version>2.0</version>
</dependency>
Java Code: Login, Get the first defect, Logout
import java.util.Base64;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
public class App {
private static final String almURL = "https://abc.hp.com/qcbin";
private static final String isAuthenticatedPath = "authentication-point/authenticate";
private static final String qcSiteSession = "rest/site-session";
private static final String logoutPath = "authentication-point/logout";
private static String lswoocookie;
private static String qcsessioncookie;
public static String strDomain = "domain";
public static String strProject = "project";
public static String strUserName = "username";
public static String strPassword = "password";
public static Client client;
public static WebTarget target;
public static Invocation.Builder invocationBuilder;
public static Response res;
private static String getEncodedAuthString() {
String auth = strUserName + ":" + strPassword;
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes());
String authHeader = "Basic " + new String(encodedAuth);
return authHeader;
}
public static void main(String args[]){
client = ClientBuilder.newBuilder().build();
/* Get LWSSO Cookie */
target = client.target(almURL).path(
isAuthenticatedPath);
invocationBuilder = target.request(new String[] { "application/xml" });
invocationBuilder.header("Authorization", getEncodedAuthString());
res = invocationBuilder.get();
lswoocookie = res.getCookies().get("LWSSO_COOKIE_KEY").getValue();
/* Get QCSession Cookie */
target = client.target(almURL).path(qcSiteSession);
invocationBuilder = target
.request();
invocationBuilder.cookie("LWSSO_COOKIE_KEY", lswoocookie);
res = invocationBuilder.post(null);
qcsessioncookie = res.getCookies().get("QCSession").getValue();
/* Get the first defect */
String midPoint = "rest/domains/" + strDomain + "/projects/" + strProject;
target = client.target(almURL).path(midPoint).path("defects/1");
invocationBuilder = target
.request(new String[] { "application/json" });
invocationBuilder.cookie("LWSSO_COOKIE_KEY", lswoocookie);
invocationBuilder.cookie("QCSession", qcsessioncookie);
res = invocationBuilder.get();
/* Logout */
target = client.target(almURL).path(logoutPath);
invocationBuilder = target
.request();
invocationBuilder.cookie("LWSSO_COOKIE_KEY", lswoocookie);
invocationBuilder.cookie("QCSession", qcsessioncookie);
res = invocationBuilder.post(null);
}
}

Related

400 Error Code while using RestTemplate for a Rest API

I have a REST API to call and I have written the client using rest template. When executing, I am getting 400 status code. The same REST API is working fine when using POSTMAN. Below are the code snippets for API and caller. Do let me know if anyone catches anything.
REST API for POST method-
#ApiOperation(value = "Download repository as zip")
#ApiResponses({#ApiResponse(code = 200, message = ""), #ApiResponse(code = 400, message = "")})
#PostMapping(value = "/download", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
public ResponseEntity<StreamingResponseBody> downloadRepository(
#RequestBody #Validated final RepositoriesRequest repositoriesRequest) {
final Situation situation = this.situationsService.getSituationId(repositoriesRequest);
if (isNull(situation)) {
return ResponseEntity.notFound().build();
} else {
final ExtractionRequest extractionRequest = new ExtractionRequest(repositoriesRequest.getType(), situation,
repositoriesRequest.getDatabase());
if (!this.validateRequest(extractionRequest)) {
return ResponseEntity.badRequest().build();
}
final ExtractionResponse response = this.extractService.extractRepository(extractionRequest);
if (null == response) {
return ResponseEntity.notFound().build();
}
final InputStream inputStream = this.extractService.getFileFromS3(response.getRepositoryPath());
if (null == inputStream) {
return ResponseEntity.noContent().build();
}
final StreamingResponseBody bodyWriter = this.bodyWriter(inputStream);
return ResponseEntity.ok()
.header("Content-Type", "application/zip")
.header(CONTENT_DISPOSITION, "attachment; filename=\"repository-" + situation.getId() + ".zip\"")
.body(bodyWriter);
}
}
REST CLIENT using Rest Template with auth token and request body as input -
HttpEntity<MultiValueMap<String, Object>> buildLoadRepoRequest(
final SimulationContext context,
final List<String> tablesName,
final String simulationId,
final Integer offset) {
final Token token = this.authenticateOkoye(simulationId, offset);
LOGGER.info("Token Run: {}", token.getAccessToken());
final String database = this.getDatabaseForEnvironment();
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(APPLICATION_JSON_UTF8);
httpHeaders.set(AUTHORIZATION, "Bearer " + token.getAccessToken());
final MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("database", database);
body.add("monthlyClosingMonth", context.getMonthlyClosingDate());
body.add("repositorySnapshot", context.getRepository());
body.add("situationId", context.getSituationId());
body.add("tableNames", tablesName);
body.add("type", context.getRunType());
return new HttpEntity<>(body, httpHeaders);
}
Exception Handler -
#Override
#ExceptionHandler(HttpClientErrorException.class)
public void loadRepository(
final SimulationContext context,
final List<String> tablesName,
final String simulationId,
final Integer offset,
final Path repositoryPath) throws IOException {
LOGGER.info("[{}] [{}] repository tablesName: {}", simulationId, offset, tablesName);
this.restTemplate.setRequestFactory(this.getClientHttpRequestFactory());
final ClientHttpResponse response = this.restTemplate.postForObject(
this.repositoriesUrl,
this.buildLoadRepoRequest(context, tablesName, simulationId, offset),
ClientHttpResponse.class);
if (response != null && HttpStatus.OK == response.getStatusCode()) {
LOGGER.info(
"response status on simulation : {} - Context: {} - status: {}",
simulationId,
offset,
response.getStatusCode());
//this.helper.copy(response.getBody(), repositoryPath);
} else if (response != null && HttpStatus.NO_CONTENT != response.getStatusCode()) {
throw new JarvisException(
"Can't retrieve RWA repository on simulation " + simulationId + " Context:" + offset);
}
}
We have been looking into this issue since yesterday and still don't have any clue. So far we have tried postForEntity, exchange, changing the headers to proper setter methods and tried passing the parameters as an object also. None of them worked.
I have a strong feeling about something being wrong at header level while calling the API.
Did you try to use httpHeaders.setContentType(MediaType.APPLICATION_JSON)
Or add consumes to #PostMapping annotation with APPLICATION_JSON_UTF8 value

how use java openid client library to get userInfo just like nodejs

I want to use access token to get userinfo with a java open-id connect library the same as nodejs.
I use npm-openid-client to get the userInfo and it works very well in nodejs
/**
** client_id and client_secret can be empty now
*/
const { Issuer } = require('openid-client');
const end_point = 'xxx'
const access_token = 'xxx'
Issuer.discover(end_point).then(function (issuer) {
const client = new issuer.Client({
client_id: 'xx',
client_secret: 'xx',
});
client.userinfo(access_token).then(function (userinfo) {
console.log('userinfo %j', userinfo);
});
});
I google java open-id library and find some library from openid.net
and finally I use connect2id
I follow the link openid-connect/userinfo and write some code below:
import java.io.*;
import java.net.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.openid.connect.sdk.claims.*;
class Test {
public static void main (String[] args) throws Exception{
String uriStr = "";
String tokenStr = "";
URI userInfoEndpoint = new URI(uriStr);
BearerAccessToken token = new BearerAccessToken(tokenStr);
// Make the request
HTTPResponse httpResponse = new UserInfoRequest(userInfoEndpoint, token)
.toHTTPRequest()
.send();
// Parse the response
UserInfoResponse userInfoResponse = UserInfoResponse.parse(httpResponse);
if (! userInfoResponse.indicatesSuccess()) {
// The request failed, e.g. due to invalid or expired token
System.out.println(userInfoResponse.toErrorResponse().getErrorObject().getCode());
System.out.println(userInfoResponse.toErrorResponse().getErrorObject().getDescription());
return;
}
// Extract the claims
UserInfo userInfo = userInfoResponse.toSuccessResponse().getUserInfo();
System.out.println("Subject: " + userInfo.getSubject());
System.out.println("Email: " + userInfo.getEmailAddress());
System.out.println("Name: " + userInfo.getName());
}
}
the result is that httpResponse return 404 not found. how can I fix it and get the userInfo ?

How to read HTTP custom error string

I'm trying to read custom error message I sent along an HTTP response for a web API project using Java.
Currently, I have this piece of Java code to read Header Response,
import java.net.HttpURLConnection;
import java.net.URL;
public class URLReader {
public static void main(String[] args) throws Exception {
URL oracle = new URL(URL);
HttpURLConnection connection =(HttpURLConnection)oracle.openConnection();
connection.setRequestMethod("GET");
connection.connect();
System.out.println(connection.getHeaderField(0));
}
}
An output of HTTP response header looks like this (Fiddler):
How can I get My Error Message text using Java?
#Abzal Kalimbetov is wrong about getErrorStream(), which will return an InputStream when an exception is raised, indicating response code >= 400 is received from the server.
Recap:
InputStream inputStream = null;
try {
// normal operation
inputStream = connection.getInputStream();
}
catch(IOException exception)
{
inputStream = connection.getErrorStream();
//#TODO you can now extract your custom error message from inputStream.
}
If the request status is greater than or equal 400, you use getErrorStream() method
if(connection.getResponseCode()>=400){
String myErrorMessage = connection.getErrorStream();
}
You can set a header in your server application which you then can read out very simple like this: ( Servlet usage is needed for this. )
private void test(ServletRequest request, ServletResponse response) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.addHeader( "myErrorMessage", "this is the message" );
String message = httpServletRequest.getHeader( "myErrorMessage" );
}
if you don't have these resources available it is possible to get the message like this:
//get all headers
Map<String, List<String>> map = connection.getHeaderFields();
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
System.out.println("Key : " + entry.getKey() +
" ,Value : " + entry.getValue());
}
//get header by 'key'
String server = connection.getHeaderField("Server");

Having trouble implementing Stormpath form Login/Authentication alongside REST oAuth authentication in the same application

We're using stormpath with Java & also trying to combine form Login with REST API authentication on the same application.
I've setup stormpath servlet plugin as described here https://docs.stormpath.com/java/servlet-plugin/quickstart.html... This works very fine.
Now, on the same application, we have APIs where I've implemented oAuth authentication with stormpath see here http://docs.stormpath.com/guides/api-key-management/
The first request for an access-token works fine by sending Basic Base64(keyId:keySecret) in the request header and grant_type = client_credentials in the body. Access tokens are being returned nicely. However trying to authenticate subsequent requests with the header Bearer <the-obtained-access-token> does not even hit the application before
returning the following json error message...
{
"error": "invalid_client",
"error_description": "access_token is invalid."
}
This is confusing because I've set breakpoints all over the application and I'm pretty sure that the API request doesn't hit the anywhere within the application before stormpath kicks in and returns this error. And even if stormpath somehow intercepts the request before getting to the REST interface, this message doesn't make any sense to me because i'm certainly making the subsequent API calls with a valid access-token obtained from the first call to get access-token.
I have run out of ideas why this could be happening but i'm suspecting that it may have something to do with stormpath config especially with a combination
of form Login/Authentication for web views and oAuth Athentication for REST endpoints. With that said, here's what my stormpath.properties looks like. Hope this could help point at anything I may be doing wrong.
stormpath.application.href=https://api.stormpath.com/v1/applications/[app-id]
stormpath.web.filters.authr=com.app.security.AuthorizationFilter
stormpath.web.request.event.listener = com.app.security.AuthenticationListener
stormpath.web.uris./resources/**=anon
stormpath.web.uris./assets/**=anon
stormpath.web.uris./v1.0/**=anon
stormpath.web.uris./** = authc,authr
stormpath.web.uris./**/**=authc,authr
Help with this would be highly appreciated.
The problem might be related to an incorrect request.
Is it possible for you to try this code in your app?:
private boolean verify(String accessToken) throws OauthAuthenticationException {
HttpRequest request = createRequestForOauth2AuthenticatedOperation(accessToken);
AccessTokenResult result = Applications.oauthRequestAuthenticator(application)
.authenticate(request);
System.out.println(result.getAccount().getEmail() + " was successfully verified, you can allow your protect operation to continue");
return true;
}
private HttpRequest createRequestForOauth2AuthenticatedOperation(String token) {
try {
Map<String, String[]> headers = new LinkedHashMap<String, String[]>();
headers.put("Accept", new String[]{"application/json"});
headers.put("Authorization", new String[]{"Bearer " + token});
HttpRequest request = HttpRequests.method(HttpMethod.GET)
.headers(headers)
.build();
return request;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
I've prepared an example that demonstrates oauth token creation as well as authorized access to protected pages using access tokens.
It builds off of the servlet example in the Stormpath SDK. The repo can be found here: https://github.com/stormpath/stormpath-java-oauth-servlet-sample
It demonstrates running a servlet application and having an out-of-band program get and use oauth tokens to access protected resources.
The core of the oauth part is in TokenAuthTest.java:
public class TokenAuthTest {
public static void main(String[] args) throws Exception {
String command = System.getProperty("command");
if (command == null || !("getToken".equals(command) || "getPage".equals(command))) {
System.err.println("Must supply a command:");
System.err.println("\t-Dcommand=getToken OR");
System.err.println("\t-Dcommand=getPage OR");
System.exit(1);
}
if ("getToken".equals(command)) {
getToken();
} else {
getPage();
}
}
private static final String APP_URL = "http://localhost:8080";
private static final String OAUTH_URI = "/oauth/token";
private static final String PROTECTED_URI = "/dashboard";
private static void getToken() throws Exception {
String username = System.getProperty("username");
String password = System.getProperty("password");
if (username == null || password == null) {
System.err.println("Must supply -Dusername=<username> -Dpassword=<password> on the command line");
System.exit(1);
}
PostMethod method = new PostMethod(APP_URL + OAUTH_URI);
method.setRequestHeader("Origin", APP_URL);
method.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
method.addParameter("grant_type", "password");
method.addParameter("username", username);
method.addParameter("password", password);
HttpClient client = new HttpClient();
client.executeMethod(method);
BufferedReader br = new BufferedReader(new InputStreamReader(method.getResponseBodyAsStream()));
String readLine;
while(((readLine = br.readLine()) != null)) {
System.out.println(readLine);
}
}
private static void getPage() throws Exception {
String token = System.getProperty("token");
if (token == null) {
System.err.println("Must supply -Dtoken=<access token> on the command line");
System.exit(1);
}
GetMethod method = new GetMethod(APP_URL + PROTECTED_URI);
HttpClient client = new HttpClient();
System.out.println("Attempting to retrieve " + PROTECTED_URI + " without token...");
int returnCode = client.executeMethod(method);
System.out.println("return code: " + returnCode);
System.out.println();
System.out.println("Attempting to retrieve " + PROTECTED_URI + " with token...");
method.addRequestHeader("Authorization", "Bearer " + token);
returnCode = client.executeMethod(method);
System.out.println("return code: " + returnCode);
}
}

2-legged OAuth and the Gmail atom feed

We're trying to get 2-legged OAuth to work with the Gmail atom feed. We're using the Java library contributed by John Kristian, Praveen Alavilli and Dirk Balfanz. [http://oauth.net/code/] instead of the GData library.
We know we have the correct CONSUMER_KEY and CONSUMER_SECRET, etc. becuase it works with the Contacts feed (http://www.google.com/m8/feeds/contacts/default/full) and have no problems. However with Gmail atom feed it always returns: HTTP/1.1 401 Unauthorized
Any ideas? Should we try a different OAuth framework or does the problem lie on the Google side?
We think we got it working with the OAuth libraries but not with the GData library.
Snippet of code is:
import static net.oauth.OAuth.HMAC_SHA1;
import static net.oauth.OAuth.OAUTH_SIGNATURE_METHOD;
import java.net.URL;
import java.util.List;
import java.util.Map;
import net.oauth.OAuthAccessor;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthMessage;
import net.oauth.ParameterStyle;
import net.oauth.SimpleOAuthValidator;
import net.oauth.client.OAuthClient;
import net.oauth.client.httpclient4.HttpClient4;
/**
* Sample application demonstrating how to do 2-Legged OAuth in the Google Data
* Java Client. See the comments below to learn about the details.
*
*/
public class GmailAtomFeed2LeggedOauth {
public static String CONSUMER_KEY = "test-1001.com";
public static String CONSUMER_SECRET = "zN0ttehR3#lSecr3+";
public static String SCOPE = "https://mail.google.com/mail/feed/atom";
public static String RESOURCE_URL = "https://mail.google.com/mail/feed/atom";
public static String SERVICE_NAME = "mail";
public static String username = "username";
public static boolean debug = true;
public static void main(String[] args) throws Exception {
// This should be passed in as a parameter
String user = username + "#" + CONSUMER_KEY;
OAuthConsumer consumer = new OAuthConsumer(null, CONSUMER_KEY, CONSUMER_SECRET, null);
OAuthAccessor accessor = new OAuthAccessor(consumer);
// HMAC uses the access token secret as a factor,
// and it's a little less compute-intensive than RSA.
accessor.consumer.setProperty(OAUTH_SIGNATURE_METHOD, HMAC_SHA1);
// Gmail only supports an atom feed
URL atomFeedUrl = new URL(SCOPE +"?xoauth_requestor_id=" + user);
System.out.println("=====================================================");
System.out.println("Building new request message...");
OAuthMessage request = accessor.newRequestMessage(OAuthMessage.GET, atomFeedUrl.toString(),null);
if (debug) {
List<Map.Entry<String, String>> params = request.getParameters();
for (Map.Entry<String, String> p : params) {
System.out.println("'" + p.getKey() + "' = <" + p.getValue() + ">");
}
System.out.println("Validating message...");
SimpleOAuthValidator validator=new SimpleOAuthValidator();
validator.validateMessage(request,accessor);
}
OAuthClient client = new OAuthClient(new HttpClient4());
System.out.println("Client invoking request message...");
System.out.println(" request: " + request);
OAuthMessage message = client.invoke(request, ParameterStyle.AUTHORIZATION_HEADER);
System.out.println("=====================================================");
System.out.println(" message: " + message.readBodyAsString());
System.out.println("=====================================================");
}
}
Put the OAuth data in the Authorization header, not on the URI.

Categories

Resources