Skip to content

Commit

Permalink
extended implementation
Browse files Browse the repository at this point in the history
Issue #122
  • Loading branch information
rsoika committed Jul 5, 2022
1 parent abb8c56 commit af93287
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 76 deletions.
28 changes: 21 additions & 7 deletions imixs-adapters-wopi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ The WOPI API endpoints /wopi/ must not be protected because LibreOffice has no m

To validate user access the imixs-adapter-wopi module provides an JWT implementation to generate and to validate an access token. The endpoint uri to access the HOST looks like this:

https://localhost:9980/{libreoffice-editor}.html?WOPISrc=http://wopi-app:8080/api/wopi/files/{your-file}?access_token={JWT}
https://localhost:9980/{libreoffice-editor}.html?WOPISrc=http://wopi-app:8080/api/wopi/files/{your-file}&access_token={JWT}


# Integration

The Imixs-WOPI Adapter provides services and a JavaScript library for a tightly coupling with the Imixs Workflow Engine. The following section shows how to integrate the Imixs-WOPI Adapter into a application. A prerequisite is that an instance of a WOPI client (e.g. LibreOffice Online) is running.
The Imixs-WOPI Adapter provides services and a JavaScript library for a tightly coupling with the Imixs Workflow Engine. The following section shows how to integrate the Imixs-WOPI Adapter into a application. A prerequisite is that an instance of a WOPI client (e.g. OnlyOffice or Collabora Online) is running.

Information about how to run LibreOffice Online (Collabora) in a Docker Container can be found [here](https://sdk.collaboraonline.com/docs/installation/CODE_Docker_image.html#).
Information about how to run LibreOffice Online (Collabora) in a Docker Container can be found [here](https://sdk.collaboraonline.com/docs/installation/CODE_Docker_image.html#). Information about OnlyOffice can be found [here](https://api.onlyoffice.com/editors/wopi/).

## Environment

Expand All @@ -49,28 +49,42 @@ To setup the Imixs-WOPI Adapter the following environment variables must be set:
| WOPI_FILE_CACHE | file path to cache wopi files temporarily on the wop host | default: /tmp/wopi/
| WOPI_POSTMESSAGEORIGIN | Optional postMessageOrigin | e.g. http://application.foo.com:8080

The following example shows a setup for in a Docker Compose file running in a local dev environment:
The following example shows a setup for in a Docker Compose file running in a local dev environment on Collaboara:

....
my-app:
image: imixs/imixs-office-workflow
environment:
....
WOPI_PUBLIC_ENDPOINT: "http://localhost:9980/loleaflet/6a844e4/loleaflet.html?"
WOPI_PUBLIC_ENDPOINT: "http://localhost:9980"
WOPI_DISCOVERY_ENDPOINT: "http://collabora-app:9980/hosting/discovery"
WOPI_HOST_ENDPOINT: "http://my-app:8080/api/wopi/"
....
ports:
- "8080:8080"
....

In a productive environment, the WOPI_PUBLIC_ENDPOINT should be set to a SSL encrypted Internet domain name:
The corresponding OnlyOffice setup looks like this:

....
my-app:
image: imixs/imixs-office-workflow
environment:
....
WOPI_PUBLIC_ENDPOINT: "https://libreoffice.foo.com/loleaflet/6a844e4/loleaflet.html?"
WOPI_PUBLIC_ENDPOINT: "http://localhost:80"
WOPI_DISCOVERY_ENDPOINT: "http://onlyoffice-app:80/hosting/discovery"
WOPI_HOST_ENDPOINT: "http://my-app:8080/api/wopi/"


In a productive environment, the WOPI_PUBLIC_ENDPOINT should be always be set to a SSL encrypted Internet domain name. The Host Endpoint and the Discovery Endpoint should point to the internal host names. :

....
my-app:
image: imixs/imixs-office-workflow
environment:
....
WOPI_PUBLIC_ENDPOINT: "https://libreoffice.foo.com"
WOPI_DISCOVERY_ENDPOINT: "http://collabora-app:9980/hosting/discovery"
WOPI_HOST_ENDPOINT: "http://my-app:8080/api/wopi/"
....
ports:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,21 @@ public class WopiAccessHandler {
void init() {
jwtPassword = WorkflowKernel.generateUniqueID();

if (wopiPublicEndpoint == null || !wopiPublicEndpoint.isPresent() || wopiPublicEndpoint.get().isEmpty()) {
// no public wopi endpoint was defined. In this case the wopi endpoints are
// resolved by parsing the wopi discovery endpoint
if (wopiDiscoveryEndpoint != null && wopiDiscoveryEndpoint.isPresent()
&& !wopiDiscoveryEndpoint.get().isEmpty()) {
try {
parseDiscoveryURL(wopiDiscoveryEndpoint.get());
} catch (SAXException | IOException | ParserConfigurationException e) {
logger.severe("Failed to parse discovery endpoint '" + wopiDiscoveryEndpoint.get() + "' Error: "
+ e.getMessage());
e.printStackTrace();
extensions = null;
mimeTypes = null;
}
} else {
logger.warning("...unable to parse discovery endpoint - parameter ' not provided!");
// no public wopi endpoint was defined. In this case the wopi endpoints are
// resolved by parsing the wopi discovery endpoint
if (wopiDiscoveryEndpoint != null && wopiDiscoveryEndpoint.isPresent()
&& !wopiDiscoveryEndpoint.get().isEmpty()) {
try {
parseDiscoveryURL(wopiDiscoveryEndpoint.get());
} catch (SAXException | IOException | ParserConfigurationException e) {
logger.severe("Failed to parse discovery endpoint '" + wopiDiscoveryEndpoint.get() + "' Error: "
+ e.getMessage());
e.printStackTrace();
extensions = null;
mimeTypes = null;
}
} else {
logger.warning("...unable to parse discovery endpoint - parameter ' not provided!");
}

}
Expand All @@ -123,7 +121,7 @@ void init() {
* cached fileData is identified by the accesstoken+filename (with a hash
* value).
* <p>
* The method also deletes outdated cached files.
* The method also deletes outdated cached files.
*
* @param jsessionid
* @param file
Expand All @@ -132,7 +130,7 @@ void init() {
public void cacheFileData(String accessToken, FileData fileData) throws IOException {
// test cache folder existence....
if (!Files.exists(Paths.get(wopiFileCache))) {
logger.info("...creating wopi cache folder '" + wopiFileCache + "'...");
logger.finest("...creating wopi cache folder '" + wopiFileCache + "'...");
Files.createDirectories(Paths.get(wopiFileCache));
}
Path filepath = getCacheFilePath(accessToken, fileData.getName());
Expand Down Expand Up @@ -177,7 +175,7 @@ public FileData fetchFileData(String accessToken, String filename) {
*/
public List<FileData> getAllFileData(String accessToken) {
List<FileData> result = new ArrayList<FileData>();
if (accessToken==null || accessToken.isEmpty()) {
if (accessToken == null || accessToken.isEmpty()) {
// no data
return result;
}
Expand All @@ -192,18 +190,18 @@ public boolean accept(File dir, String name) {
return name.startsWith(prafix);
}
});

// if no files found then exit
if (foundFiles==null) {
if (foundFiles == null) {
return result;
}

// Process files
for (File file : foundFiles) {
byte[] content = Files.readAllBytes(Paths.get(wopiFileCache + file.getName()));
String filename=file.getName();
String filename = file.getName();
// cut prefix
filename=filename.substring(prafix.length()+1);
filename = filename.substring(prafix.length() + 1);
logger.finest("......found cached file : " + filename);
FileData fileData = new FileData(filename, content, null, null);
result.add(fileData);
Expand All @@ -216,14 +214,14 @@ public boolean accept(File dir, String name) {
return result;
}


/**
* Deletes all existing files cached for a given token.
*
* @param accessToken
*/
public void clearFileCache(String accessToken) {

if (accessToken==null || accessToken.isEmpty()) {
if (accessToken == null || accessToken.isEmpty()) {
// no data
return;
}
Expand All @@ -237,7 +235,7 @@ public boolean accept(File dir, String name) {
return name.startsWith(prafix);
}
});
if (foundFiles!=null) {
if (foundFiles != null) {
// delete files...
for (File file : foundFiles) {
Files.delete(Paths.get(wopiFileCache + file.getName()));
Expand All @@ -247,7 +245,7 @@ public boolean accept(File dir, String name) {
logger.severe("..failed to delete file: " + e.getMessage());
}
}

/**
* Generates a new access token
*
Expand Down Expand Up @@ -346,25 +344,19 @@ public String purgeAccessToken(String access_token) {
* @return
*/
public String getClientEndpointByFilename(String filename) {

// do we have a public wopi client endpoint?
if (wopiPublicEndpoint != null || wopiPublicEndpoint.isPresent() && !wopiPublicEndpoint.get().isEmpty()) {
return wopiPublicEndpoint.get();
}

String result = null;
// resolving endpoint by discovery url.....
if (extensions == null) {
// lazy initalizing...
init();
}

if (extensions != null && filename.contains(".")) {
String ext = filename.substring(filename.lastIndexOf('.') + 1);
return extensions.get(ext);
result = extensions.get(ext);
}

logger.warning("...no wopi client endpoint could be resolved!");
return null;
// finally we replace the host name with the publicEndpoint
return resolvePublicEndpoint(result);
}

/**
Expand All @@ -377,23 +369,53 @@ public String getClientEndpointByFilename(String filename) {
* @return
*/
public String getClientEndpointByMimeType(String mimeType) {
// do we have a public wopi client endpoint?
if (wopiPublicEndpoint != null || wopiPublicEndpoint.isPresent() && !wopiPublicEndpoint.get().isEmpty()) {
return wopiPublicEndpoint.get();
}

String result = null;
// resolving endpoint by discovery url.....
if (extensions == null) {
// lazy initalizing...
init();
}

if (mimeTypes != null) {
return mimeTypes.get(mimeType);
result = mimeTypes.get(mimeType);
}
// finally we replace the host name with the publicEndpoint
return resolvePublicEndpoint(result);
}

logger.warning("...no wopi client endpoint could be resolved!");
return null;
/**
* Helper Method to replace the internal host name with the public endpoint
*
* @param uri
* @return
*/
private String resolvePublicEndpoint(String uri) {
String result = null;
if (wopiPublicEndpoint != null && wopiPublicEndpoint.isPresent() && !wopiPublicEndpoint.get().isEmpty()) {

try {
URL internalURL = new URL(uri);

String internalFile = internalURL.getFile();

String publicEndpoint = wopiPublicEndpoint.get();
if (publicEndpoint.endsWith("/")) {
publicEndpoint = publicEndpoint.substring(0, publicEndpoint.length() - 1);
}
result = publicEndpoint + internalFile;

logger.info("resolved public Endpint: " + result);

} catch (MalformedURLException e) {
e.printStackTrace();
}

} else {
logger.warning("...wopi.public.endpoint is not set - check configuration!");

}

return result;
}

/**
Expand Down Expand Up @@ -429,7 +451,7 @@ void parseDiscoveryURL(String endpoint)
if (appNode.getNodeType() == Node.ELEMENT_NODE) {
Element eElement = (Element) appNode;
String appName = eElement.getAttribute("name");
logger.info("...app=" + appName);
logger.finest("...app=" + appName);
// now get all action urls...
NodeList actionElements = eElement.getElementsByTagName("action");
for (int j = 0; j < actionElements.getLength(); j++) {
Expand All @@ -442,12 +464,12 @@ void parseDiscoveryURL(String endpoint)

if (actionExt != null && !actionExt.isEmpty()) {
extensions.put(actionExt, actionurlsrc);
logger.info("...ext=" + actionExt + " -> " + actionurlsrc);
logger.finest("...ext=" + actionExt + " -> " + actionurlsrc);
} else {
// this can be a mimetype...
if (appName.contains("/")) {
mimeTypes.put(appName, actionurlsrc);
logger.info("...mimetype=" + appName + " -> " + actionurlsrc);
logger.finest("...mimetype=" + appName + " -> " + actionurlsrc);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
* The controller also listens on the WorklfowEvent to update modified file
* content into the workItem before processing.
*
* @author rsoika
* @author rsoika
*
*/
@Named
Expand Down Expand Up @@ -185,7 +185,7 @@ public String getWopiAccessURL(String uniqueid, String file, String userid, Stri
}

String token = generateAccessToken(userid, username);
baseURL = baseURL + "WOPISrc=" + wopiHostEndpoint + uniqueid + "/files/" + file + "?access_token=" + token;
baseURL = baseURL + "WOPISrc=" + wopiHostEndpoint + uniqueid + "/files/" + file + "&access_token=" + token;
if (baseURL.startsWith("http://")) {
logger.fine("...WOPI Client is running without SSL - this is not recommended for production!");
}
Expand Down
Loading

0 comments on commit af93287

Please sign in to comment.