Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.


Background

Mime messages are text, encoded and wrapped up for transmission.  JEMH uses this format for audit history and test cases, it should also expose a REST API for external tools to drive JEMH via REST:

No Formatcode
MIME-Version: 1.0
Received: by 10.223.112.12 with HTTP; Sat, 18 Jun 2011 22:42:26 -0700 (PDT)
Date: Sun, 19 Jun 2011 17:42:26 +1200
Message-ID: <BANLkTinB1mfSh+GwOXGNWoL4SyDvOpdBoQ@mail.gmail.com>
Subject: This is a starting email template, update as required
From: "Andy Brook" <andy@localhost>
To: changeme@thiswontwork.com
Content-Type: text/plain; charset=UTF-8

some text

Image Removed

Experimental Feature


Required Info

In order for JEMH to process a mail, it needs to use a Profile.  Until now Profiles are tied to mailboxes, allowing arbitrary external use of those profiles may trigger unexpected scenarios, therefore:

  • Profiles will need to explicitly opt-in to API use

Image Removed
  • Image Added
  • Lists of profiles via REST will only show those that have opted-in.

  • All REST API's require an authenticated user

API

URLS must follow this pattern

  • create POST /urlOfRestResource

Changes in JEMH 5.0 (Jira 10+)

In JEMH 5.0, JEMH will not process mail directly through the API, but will store this mail in a nominated folder (JIRA_SHARED_HOME/import/mail/nnn) that is configured as a source for a Jira mail handler. This addresses scalability problems, and removes a source of denial of service.

API

URLS must follow this pattern

  • create POST /urlOfRestResource

  • read GET /urlOfRestResource[/id]

  • update PUT /urlOfRestResource/id

  • delete DELETE /urlOfRestResource/id

URL

Method

Response

.../rest/jemh/latest/public/profile/list

GET

List of JSON profile objects that identify Profile NAME and ID.

Requires authenticated user (base 64 headers?)

Request:

noformat
Code Block
curl -D- -u admin:admin -X GET http://dev-jira:8080/rest/jemh/latest/public/profile/list

Response:

noformat
Code Block
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-AREQUESTID: 717x157x1
X-ASEN: SEN-2050663
Set-Cookie: JSESSIONID=54EBDC61B68527A9C48C9106A76054E0; Path=/; HttpOnly
X-Seraph-LoginReason: OK
Set-Cookie: atlassian.xsrf.token=B3QZ-T2ZD-68A2-PNV6|c01f148e72fd5ea6a08f5a62276e077754c7005a|lin; Path=/
X-ASESSIONID: 1c9uhsa
X-AUSERNAME: admin
Cache-Control: no-cache, no-store, no-transform
X-Content-Type-Options: nosniff
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 24 Sep 2014 10:57:58 GMT
[{"id":1,"name":"Testing","desc":"TEST"}]


/rest/jemh/latest/public/mailbox/deliver

POST

Request

Using an email like:

noformat
Code Block
MIME-Version: 1.0
Received: by 10.223.112.12 with HTTP; Sat, 18 Jun 2011 22:42:26 -0700 (PDT)
Date: Sun, 19 Jun 2011 17:42:26 +1200
Message-ID: <BANLkTinB1mfSh+GwOXGNWoL4SyDvOpdBoQ@mail.gmail.com>
Subject: This is a starting email template, update as required
From: "Andy Brook" <andy@localhost>
To: changeme@thiswontwork.com
Content-Type: text/plain; charset=UTF-8
some text

A CURL command would be of format (NOTE: initially application/x-www-form-urlencoded was supported, since 1.6.15, multipart/form-data is also supported):

noformat
Code Block
curl -v -u admin:admin -H "Content-Type: multipart/form-data" --data "profileId=1&email=...." -X POST http://dev-jira:8080/rest/jemh/latest/public/mailbox/deliver

URLEncoding the content (shown above) eg through services like http://www.url-encode-decode.com/ and replacing .... with the result:

noformat
Code Block
curl -v -u admin:admin -H "Content-Type: multipart/form-data" --data "profileId=1&email=MIME-Version%3A+1.0%0AReceived%3A+by+10.223.112.12+with+HTTP%3B+Sat%2C+18+Jun+2011+22%3A42%3A26+-0700+%28PDT%29%0ADate%3A+Sun%2C+19+Jun+2011+17%3A42%3A26+%2B1200%0AMessage-ID%3A+%3CBANLkTinB1mfSh%2BGwOXGNWoL4SyDvOpdBoQ%40mail.gmail.com%3E%0ASubject%3A+This+is+a+starting+email+template%2C+update+as+required%0AFrom%3A+%22Andy+Brook%22+%3Candy%40localhost%3E%0ATo%3A+changeme%40thiswontwork.com%0AContent-Type%3A+text%2Fplain%3B+charset%3DUTF-8%0A%0Asome+text" -X POST http://dev-jira:8080/rest/jemh/latest/public/mailbox/deliver

RESPONSE format:

noformat
Code Block
{ 
createdIssues [] {id,key,self},
updatedIssues [] {id,key,self},
commentedIssues [] {id,key,self},
filterResult {name, action, reason},
hints [] {hintText, causedRejection, providedValue, validValues}
error,
detail,
processingException,
isForwarded,
processTime,
processOutcome
}

Example Response:

noformat
Code Block
{"createdIssues":[{"id":15910,"key":"TEST-29","self":"http://dev-jira:8080/rest/api/2/issue/15910"}],"processTime":651,"processOutcome":
"canHandle","forwarded":false}Worked JAVA Examples
"canHandle","forwarded":false}


(warning) JEMH 5.0+ requires you to set a FOLDER in the Profile for API mail delivery, in order to be then picked up through a standard Jira Mail Handler config.


Example script to deliver a mail

The script below will use the first param as the profileID, update the url as applicable, the response will be launched in a browser

Code Block
#!/bin/bash
if [[ $# -eq 0 ]] ; then
    echo "Usage: send <profileid>"
    exit 1
fi

EML=`cat multipart-alternative.txt`

curl -v -u admin:admin -H "Content-Type: application/x-www-form-urlencoded" --data "profileId=$1" --data-urlencode "email=$EML" -X POST http://localhost:2990/jira/rest/jemh/latest/public/mailbox/deliver > out.html
firefox out.html &

Worked JAVA Examples

JDK11 HttpClient starter code:

Code Block
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

...

	public static final String  FORM_PARAM_PROFILE_ID = "profileId";
	public static final String  HEADER_AUTHORIZATION = "Authorization";
	public static final String  HEADER_CONTENT_TYPE = "Content-Type";
	public static final String  FORM_URLENCODED = "application/x-www-form-urlencoded";
	
		// baseurl
		String baseUrl = ComponentAccessor.getApplicationProperties().getString(APKeys.JIRA_BASEURL);
		Assert.assertNotNull(baseUrl);
		String targetUrl = baseUrl + "/rest/jemh/latest/public/mailbox/deliver";
		
		Map<String,String> urlParameters=new HashMap<>();
		urlParameters.put("profileId", ""+profile.getProfileId());
		urlParameters.put("email", emailStr);
		
		String urlEncoded = urlParameters.entrySet()
                .stream()
                .map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
                .collect(Collectors.joining("&"));
		HttpRequest.BodyPublisher encodedParams = HttpRequest.BodyPublishers.ofString(urlEncoded);		
		
		HttpClient client = HttpClient.newHttpClient();
		
		HttpRequest request = HttpRequest.newBuilder()
				.version(HttpClient.Version.HTTP_1_1)
		        .uri(new URI(targetUrl))
		        .POST(encodedParams)		        
		        .header(HEADER_CONTENT_TYPE, FORM_URLENCODED)
		        .header(HEADER_AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString("admin:admin".getBytes())).build();

		HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
		System.out.println(response.body());
		Assert.assertTrue("Connection was not authenticated", response.statusCode()!=401);
Example Test Case
Code Block
languagejava
titleExample Test Caselinenumberstrue
package com.javahollic.jira.emh.publicapi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Test;
public class TestPost
{
	@Test
	public void testPost() throws IOException
	{
		InputStream is = getClass().getClassLoader().getResourceAsStream("plaintextEmail.txt");
		Assert.assertNotNull(is);
		String emailStr=IOUtils.toString(is,"UTF-8");
				
		String name = "admin";
		String password = "admin";
		String authString = name + ":" + password;
		System.out.println("auth string: " + authString);
		byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
		String authStringEnc = new String(authEncBytes);
		System.out.println("Base64 encoded auth string: " + authStringEnc);
		HttpClient client = new HttpClient();
		client.getParams().setParameter("http.useragent", "Test Client");
		BufferedReader br = null;
		PostMethod method = new PostMethod("http://dev-jira:8080/rest/jemh/latest/public/mailbox/deliver");
		method.addParameter("profileId", "1");
		method.addParameter("email", emailStr);
		method.addRequestHeader("Authorization", "Basic " + authStringEnc);
		try
		{
			int returnCode = client.executeMethod(method);
			if (returnCode == HttpStatus.SC_NOT_IMPLEMENTED)
			{
				System.err.println("The Post method is not implemented by this URI");
				// still consume the response body
				System.err.println("\n"+method.getResponseBodyAsString());
			}
			else
			{
				String response=IOUtils.toString(method.getResponseBodyAsStream());
				System.out.println("\n-------------------------\n"+response+"\n-------------------------\n");
			}
		}
		catch (Exception e)
		{
			System.err.println(e);
		}
		finally
		{
			method.releaseConnection();
			if (br != null)
				try
				{
					br.close();
				}
				catch (Exception fe)
				{
				}
		}
	}
	@Test
	public void testGet()
	{
		try
		{
			String webPage = "http://dev-jira:8080/rest/jemh/latest/public/profile/list";
			String name = "admin";
			String password = "admin";
			String authString = name + ":" + password;
			System.out.println("auth string: " + authString);
			byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
			String authStringEnc = new String(authEncBytes);
			System.out.println("Base64 encoded auth string: " + authStringEnc);
			URL url = new URL(webPage);
			URLConnection urlConnection = url.openConnection();
			urlConnection.setRequestProperty("Authorization", "Basic " + authStringEnc);
			InputStream is = urlConnection.getInputStream();
			InputStreamReader isr = new InputStreamReader(is);
			int numCharsRead;
			char[] charArray = new char[1024];
			StringBuffer sb = new StringBuffer();
			while ((numCharsRead = isr.read(charArray)) > 0)
			{
				sb.append(charArray, 0, numCharsRead);
			}
			String result = sb.toString();
			System.out.println("*** BEGIN ***");
			System.out.println(result);
			System.out.println("*** END ***");
		}
		catch (MalformedURLException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
}
 

Related articles

Filter by label (Content by label)
showLabelsfalse
max5
spacesJEMH
showSpacefalse
sortmodified
typepage
reversetrue
typepage
labelsevent listener jemh issue notification
cqllabel in ( "rest" , "java" , "jira" , "issue-creation" ) and space = "JEMH"
labelsevent listener jemh issue notification
Page Properties
hiddentrue


Related issues