Skip to content

Commit

Permalink
fixes igniterealtime#1: Migrate to Swagger
Browse files Browse the repository at this point in the history
This commit introduces an automatically generated Swagger-based REST client on /plugins/restapi/docs/index.html

Note that this can't be used 'offline'. We still need to replace the static documentation (in readme.html) with something that's generated during compilation/packaging (issue igniterealtime#71)
  • Loading branch information
guusdk committed Jan 13, 2022
1 parent b4dd0d8 commit c412f60
Show file tree
Hide file tree
Showing 23 changed files with 311 additions and 6 deletions.
1 change: 1 addition & 0 deletions changelog.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ <h1>
<p><b>1.7.0</b> (tbd)</p>
<ul>
<li>Requires Openfire 4.7.0</li>
<li>[<a href='https://github.com/igniterealtime/openfire-restAPI-plugin/issues/1'>#1</a>] - Migrate to Swagger.</li>
<li>[<a href='https://github.com/igniterealtime/openfire-restAPI-plugin/issues/25'>#25</a>] - Java 11 Jaxb issue</li>
<li>[<a href='https://github.com/igniterealtime/openfire-restAPI-plugin/issues/39'>#39</a>] - Requests fail when using XML as accept type on Openfire servers running java 11</li>
<li>[<a href='https://github.com/igniterealtime/openfire-restAPI-plugin/issues/67'>#67</a>] - Updated Jersey dependency to 2.35.</li>
Expand Down
2 changes: 1 addition & 1 deletion plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<description>Allows administration over a RESTful API.</description>
<author>Roman Soldatow</author>
<version>${project.version}</version>
<date>12/17/2021</date>
<date>01/07/2022</date>
<minServerVersion>4.7.0</minServerVersion>
<adminconsole>
<tab id="tab-server">
Expand Down
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
<version>2.1.11</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2-servlet-initializer-v2</artifactId>
<version>2.1.11</version>
</dependency>
</dependencies>

<profiles>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public class AuthFilter implements ContainerRequestFilter {

@Override
public void filter(ContainerRequestContext containerRequest) throws IOException {
if (containerRequest.getUriInfo().getRequestUri().getPath().equals("/plugins/restapi/v1/openapi.yaml")) {
LOG.debug("Authentication was bypassed for openapi.yaml file (documentation)");
return;
}

if (!plugin.isEnabled()) {
LOG.debug("REST API Plugin is not enabled");
throw new WebApplicationException(Status.FORBIDDEN);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2022 Ignite Realtime Foundation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.openfire.plugin.rest.service;

import io.swagger.v3.jaxrs2.integration.resources.BaseOpenApiResource;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.glassfish.jersey.server.ServerProperties;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.PluginMetadataHelper;
import org.jivesoftware.openfire.plugin.rest.RESTServicePlugin;

import javax.servlet.ServletConfig;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.*;
import java.util.Collections;

/**
* Configuration for the REST API.
*
* @author Guus der Kinderen, [email protected]
*/
@Path("restapi/v1/")
public class CustomOpenApiResource extends BaseOpenApiResource {
@Context
ServletConfig config;

@Context
Application app;

public CustomOpenApiResource() {
final RESTServicePlugin plugin = (RESTServicePlugin) XMPPServer.getInstance().getPluginManager().getPluginByName("REST API").orElse(null);
final String version = plugin != null ? PluginMetadataHelper.getVersion(plugin).getVersionString() : "(unknown)";

openApiConfiguration = new SwaggerConfiguration();

final OpenAPI openAPI = new OpenAPI()
// 'server' is needed to be able to add the additional '/plugins' context root.
.servers(Collections.singletonList(new Server().url("/plugins")))

.info(new Info()
.description("This is the documentation for a REST API of the Openfire Real-time communication server.")
.title("Openfire REST API")
.contact(new Contact()
.name("Ignite Realtime Foundation")
.url("https://www.igniterealtime.org")
)
.version(version)
.license(new License()
.name("Apache 2.0")
.url("http://www.apache.org/licenses/LICENSE-2.0.html"))
);

openAPI.components(new Components());

final String key;
if (plugin == null || !"basic".equals(plugin.getHttpAuth())) {
key = "Secret key auth";
final SecurityScheme apiKeyScheme = new SecurityScheme();
apiKeyScheme.setDescription("Authenticate using the Secret Key as configured in the Openfire admin console.");
apiKeyScheme.setType(SecurityScheme.Type.APIKEY);
apiKeyScheme.setName("Authorization");
apiKeyScheme.setIn(SecurityScheme.In.HEADER);
openAPI.getComponents().addSecuritySchemes(key, apiKeyScheme);
} else {
key = "Admin Console account";
final SecurityScheme basicAuthScheme = new SecurityScheme();
basicAuthScheme.setDescription("Authenticate using a valid admin account for Openfire admin console.");
basicAuthScheme.setType(SecurityScheme.Type.HTTP);
basicAuthScheme.setScheme("basic");
openAPI.getComponents().addSecuritySchemes(key, basicAuthScheme);
}
final SecurityRequirement securityItem = new SecurityRequirement();
securityItem.addList(key);
openAPI.addSecurityItem(securityItem);

((SwaggerConfiguration)openApiConfiguration).openAPI(openAPI);
}

@GET
@Path("openapi.yaml")
@Produces({"application/yaml"})
@Operation(hidden = true)
public Response getOpenApiYaml(@Context HttpHeaders headers,
@Context UriInfo uriInfo) throws Exception {

return super.getOpenApi(headers, config, app, uriInfo, "yaml");
}

@GET
@Path("openapi.json")
@Produces({MediaType.APPLICATION_JSON})
@Operation(hidden = true)
public Response getOpenApiJson(@Context HttpHeaders headers,
@Context UriInfo uriInfo) throws Exception {

return super.getOpenApi(headers, config, app, uriInfo, "json");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import org.jivesoftware.openfire.plugin.rest.CORSFilter;
import org.jivesoftware.openfire.plugin.rest.exceptions.RESTExceptionMapper;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletConfig;
import javax.ws.rs.core.Context;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -72,10 +73,13 @@ public String loadAuthenticationFilter() {
/**
* Instantiates a new jersey wrapper.
*/
public JerseyWrapper() {
public JerseyWrapper(@Context ServletConfig servletConfig) {

// Filters
loadAuthenticationFilter();
register(CORSFilter.class);

// Services
registerClasses(
GroupService.class,
MessageService.class,
Expand All @@ -98,6 +102,9 @@ public JerseyWrapper() {

// Exception mapper
register(RESTExceptionMapper.class);

// Documentation (Swagger)
register( new CustomOpenApiResource() );
}

/*
Expand Down
Binary file added src/web/docs/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/web/docs/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
62 changes: 62 additions & 0 deletions src/web/docs/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="decorator" content="none"/> <!-- Openfire specific: do not use the Admin Console layout. -->
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="swagger-ui.css" />
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}

*,
*:before,
*:after
{
box-sizing: inherit;
}

body
{
margin:0;
background: #fafafa;
}
</style>
</head>

<body>
<div id="swagger-ui"></div>

<script src="swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script>
window.onload = function() {
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "http://localhost:9090/plugins/restapi/v1/openapi.yaml",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
// End Swagger UI call region

window.ui = ui;
};
</script>
</body>
</html>
75 changes: 75 additions & 0 deletions src/web/docs/oauth2-redirect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;

if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}

arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};

isValid = qp.state === sentState;

if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}

if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}

oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}

window.addEventListener('DOMContentLoaded', function () {
run();
});
</script>
</body>
</html>
3 changes: 3 additions & 0 deletions src/web/docs/swagger-ui-bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/web/docs/swagger-ui-bundle.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/web/docs/swagger-ui-es-bundle-core.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/web/docs/swagger-ui-es-bundle-core.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/web/docs/swagger-ui-es-bundle.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/web/docs/swagger-ui-es-bundle.js.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/web/docs/swagger-ui-standalone-preset.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/web/docs/swagger-ui-standalone-preset.js.map

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/web/docs/swagger-ui.css

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/web/docs/swagger-ui.css.map

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/web/docs/swagger-ui.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/web/docs/swagger-ui.js.map

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions src/web/rest-api.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,7 @@
</ul>

<p>You can find here detailed documentation over the Openfire REST API:
<a
href="/plugin-showfile.jsp?plugin=restapi&showReadme=true&decorator=none">REST
API Documentation</a>
<a href="docs/index.html" target="_blank">REST API Documentation (opens in new tab)</a>
</p>
</div>
</fieldset>
Expand Down

0 comments on commit c412f60

Please sign in to comment.