Skip to content

Commit

Permalink
documentation, environment variables
Browse files Browse the repository at this point in the history
Issue #108
  • Loading branch information
rsoika committed May 12, 2021
1 parent a848ee4 commit 9195325
Show file tree
Hide file tree
Showing 6 changed files with 507 additions and 63 deletions.
42 changes: 42 additions & 0 deletions imixs-adapters-wopi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ The WOPI Adapter module provides a Rest API with the following endpoints. The en





### Security

The WOPI API endpoints /wopi/ must not be protected because LibreOffice has no mechanism to authenticate against a WOPI Host. You need to make sure the endpoint /wopi/ is not protected by the web.xml.
Expand All @@ -31,6 +33,46 @@ To validate user access the imixs-adapter-wopi module provides an JWT implementa

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.

## Environment

To setup the Imixs-WOPI Adapter the following environment variables must be set:


| Variable | Description | Example |
| --------------------- |---------------------------------------|---------|
| WOPI_PUBLIC_ENDPOINT | Public client endpoint to be called by the web appliacation. This endpoint should be SSL encrypted |https://libreoffice.foo.com/loleaflet/6a844e4/loleaflet.html?
| WOPI_HOST_ENDPOINT | Internal Wopi Host endpoint is called by the Wopi Client to fetch and store file data. This endpoint should not be public accessible | http://my-app:8080/api/wopi/
| WOPI_DISCOVERY_ENDPOINT | Optional public discovery endpoint used by the Wopi Host implementation to resolve the public wopi endpoint dynamically. This variable should only be set if no WOPI_PUBLIC_ENDPOINT was defined! | http://localhost:9980/hosting/discovery


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

....
my-app:
image: imixs/imixs-office-workflow
environment:
....
WOPI_PUBLIC_ENDPOINT: "http://localhost:9980/loleaflet/6a844e4/loleaflet.html?"
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:

....
my-app:
image: imixs/imixs-office-workflow
environment:
....
WOPI_PUBLIC_ENDPOINT: "https://libreoffice.foo.com/loleaflet/6a844e4/loleaflet.html?"
WOPI_HOST_ENDPOINT: "http://my-app:8080/api/wopi/"
....
ports:
- "8080:8080"
....

## Maven

The imixs-adapter-wopi module can be added into an Imixs Workflow application. The module provides CDI and Rest API components.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@
/**
* The WopiAccessHandler provides methods to generate 'access_token's and also
* computes the WOPI Client endpoints from the discovery URL.
* <p>
* The wopi client endpoint can be set by the environment variable
* WOPI_PUBLIC_ENDPOINT
* <p>
* Optional a discoveryEndpoint (WOPI_DISCOVERY_ENDPOINT) can be set to resolve
* the WOPI Client endpoints dynamically. In most cases this is not necessary
* and a wopi.public.endpoint can be set.
*
*
* @author rsoika
* @version 1.0
Expand All @@ -50,12 +58,15 @@ public class WopiAccessHandler {
private Map<String, String> extensions = null;
private Map<String, String> mimeTypes = null;
private Map<String, FileData> fileDataCache = null;


@Inject
@ConfigProperty(name = "wopi.discovery.endpoint")
Optional<String> wopiDiscoveryEndpoint;

@Inject
@ConfigProperty(name = "wopi.public.endpoint")
Optional<String> wopiPublicEndpoint;

@Inject
@ConfigProperty(name = "wopi.access.token.expiration", defaultValue = "3600") // 1 hour
long wopiAccessTokenExpiration;
Expand All @@ -64,34 +75,43 @@ public class WopiAccessHandler {

/**
* PostContruct event - generate a jwt password to compute the access tokens.
* <p>
* Optional a discoveryEndpoint can be set to resolve the WOPI Client endpoints
* dynamically. In most cases this is not necessary and a wopi.public.endpoint
* can be set.
*
*/
@PostConstruct
void init() {
jwtPassword = WorkflowKernel.generateUniqueID();
fileDataCache = new HashMap<String, FileData>();

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;
if (wopiPublicEndpoint == null || !wopiPublicEndpoint.isPresent() || wopiPublicEndpoint.get().isEmpty()) {
// no public wopi endpoint was defined. In this case the wop 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!");
}
} else {
logger.warning("...unable to parse discovery endpoint - parameter ' not provided!");
}

}

/**
* This method stores a fileData into the application scoped file cache.
* <p>
* The key to store the object is the access token which is unique within the current user context
* The key to store the object is the access token which is unique within the
* current user context
*
* @param jsessionid
* @param file
Expand All @@ -108,7 +128,8 @@ public void putFileData(String accessToken, FileData fileData) {
}

/**
* This method is called by the WopiController to fetch a file by the access token.
* This method is called by the WopiController to fetch a file by the access
* token.
* <p>
*
* @param accessToken
Expand All @@ -127,12 +148,12 @@ public FileData fetchFileData(String accessToken) {
* @return
* @throws JWTException
*/
public String generateAccessToken( String userid, String username) throws JWTException {
public String generateAccessToken(String userid, String username) throws JWTException {
SecretKey secretKey = HMAC.createKey("HmacSHA256", jwtPassword.getBytes());
String payload = "{\"sub\":\"wopi-host\"";
payload=payload +",\"userid\":\"" + userid + "\"";
payload=payload +",\"username\":\"" + username + "\"";
payload=payload + "}";
payload = payload + ",\"userid\":\"" + userid + "\"";
payload = payload + ",\"username\":\"" + username + "\"";
payload = payload + "}";
JWTBuilder builder = new JWTBuilder().setKey(secretKey).setPayload(payload);

return builder.getToken();
Expand All @@ -145,7 +166,8 @@ public String generateAccessToken( String userid, String username) throws JWTExc
* Collabora sends additional query params starting with '?' which is not
* expected here!
* <p>
* In case the token is valid, the method returns the paylod, otherwise the method returns null.
* In case the token is valid, the method returns the paylod, otherwise the
* method returns null.
*
* @param access_token
* @return the token payload or null if the token is not valid
Expand All @@ -158,8 +180,8 @@ public JsonObject validateAccessToken(String access_token) {
}

// clean unexpected query params
access_token=purgeAccessToken(access_token);
access_token = purgeAccessToken(access_token);

// We need the secret key...
SecretKey secretKey = HMAC.createKey("HmacSHA256", jwtPassword.getBytes());
try {
Expand Down Expand Up @@ -199,7 +221,7 @@ public String purgeAccessToken(String access_token) {
if (access_token == null || access_token.isEmpty()) {
return access_token;
}
// test if a '?' is included and remove that part
// test if a '?' is included and remove that part
if (access_token.contains("?")) {
// clean token....
access_token = access_token.substring(0, access_token.indexOf("?"));
Expand All @@ -210,38 +232,61 @@ public String purgeAccessToken(String access_token) {

/**
* Returns a WOPI Client endpoint by a file extension
* <p>
* If wopi.public.endpoint is set than this endpoint will be returned. If not
* the endpoint is resolved dynamically by the wopi.desovery.endpoint
*
* @param ext
* @return
*/
public String getClientEndpointByFilename(String filename) {
// lazy initalizing...

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

// 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);
}

logger.warning("...no wopi client endpoint could be resolved!");
return null;
}

/**
* Returns a WOPI Client endpoint by a file mime type
* <p>
* If wopi.public.endpoint is set than this endpoint will be returned. If not
* the endpoint is resolved dynamically by the wopi.desovery.endpoint
*
* @param ext
* @return
*/
public String getClientEndpointByMimeType(String mimeType) {
// lazy initalizing...
// do we have a public wopi client endpoint?
if (wopiPublicEndpoint != null || wopiPublicEndpoint.isPresent() && !wopiPublicEndpoint.get().isEmpty()) {
return wopiPublicEndpoint.get();
}

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

if (mimeTypes != null) {
return mimeTypes.get(mimeType);
}

logger.warning("...no wopi client endpoint could be resolved!");
return null;
}

Expand All @@ -260,7 +305,7 @@ public String getClientEndpointByMimeType(String mimeType) {
void parseDiscoveryURL(String endpoint)
throws MalformedURLException, SAXException, IOException, ParserConfigurationException {

logger.info("...discovering WOPI Client endpoints: " + endpoint);
logger.info("...parsing wopi.discovery.endpoint: " + endpoint);

extensions = new HashMap<String, String>();
mimeTypes = new HashMap<String, String>();
Expand Down
6 changes: 6 additions & 0 deletions imixs-adapters-wopi/src/main/resources/META-INF/beans.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
bean-discovery-mode="all" version="2.0">
</beans>
24 changes: 24 additions & 0 deletions imixs-adapters-wopi/src/main/resources/META-INF/copyright.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Imixs Workflow
Copyright (C) 2001, 2011 Imixs Software Solutions GmbH,
http://www.imixs.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You can receive a copy of the GNU General Public
License at http://www.gnu.org/licenses/gpl.html

Project:
http://www.imixs.org
http://java.net/projects/imixs-workflow

Contributors:
Imixs Software Solutions GmbH - initial API and implementation
Ralph Soika - Software Developer
Loading

0 comments on commit 9195325

Please sign in to comment.