Writing a Plugin

1. Overview

Passport provides a plugin mechanism that loads plugins at startup rather than at runtime. This provides much better performance than a runtime plugin system. Therefore, you must restart Passport anytime a plugin is installed.

There are two steps to adding a plugin to Passport:

  1. Write the plugin by implementing the correct plugin interface

  2. Install the plugin into your Passport installation

2. Writing a Plugin

First, you need to create a plugin project. If you use an IDE, you might also want to setup the classpath for the project to include all of the Passport JAR files. You can also use whatever Java build system you prefer (Savant, Maven, Ant, Gradle, etc). Our examples will use the Savant build system since that is the build system Inversoft uses internally and all of the necessary artifacts needed to build plugins are easily retrieved using Savant.

Create a directory for our plugin, the source code, the Passport libraries (JAR files) and our tests. Here is a common directory structure for Java projects:

Plugin Project Layout
my-plugin
  |
  |- src
  |   |- main
  |   |   |- java
  |   |
  |   |- test
  |       |- java
  |
  |- build.savant

Following the Java convention of using packages for all classes, you will want to create sub-directories under src/main/java and src/test/java that identify the package for your plugin. For example, you might create a directory under each called com/mycompany/passport/plugins that will contain your plugin code.

Your project is now ready for you to start coding.

2.1. Install Java 8

If you haven’t already, you need to download the Java 8 JDK. This contains all of the tools you need to compile Java projects and create JAR files. You can download the Java 8 JDK from the Oracle site at http://www.oracle.com/technetwork/java/javase/overview/index.html.

2.2. Setup Savant

If you want to follow our examples, you’ll need to install the Savant build tool. You can execute these commands on a Linux or Mac machine to install Savant:

$ mkdir ~/savant
$ cd ~/savant
$ wget http://savant.inversoft.org/org/savantbuild/savant-core/1.0.0/savant-1.0.0.tar.gz
$ tar -xvfz savant-1.0.0.tar.gz
$ ln -s ./savant-1.0.0 current
$ export PATH=$PATH:~/savant/current/bin/

2.3. Create a Build File

After you create your project directory, create the Savant build file named build.savant. Here is a simple build file example:

Savant Build File
savantVersion = "1.0.0"

project(group: "com.inversoft.passport", name: "test-plugin", version: "0.1.0", licenses: ["ApacheV2_0"]) {
  workflow {
    standard()
  }

  publishWorkflow {
    subversion(repository: "http://svn.inversoft.com/internal/savant")
  }

  dependencies {
    group(name: "compile") {
      dependency(id: "com.inversoft.passport:passport-plugin-api:1.0.10")
      dependency(id: "com.google.inject:guice:4.0.0")
      dependency(id: "com.google.inject.extensions:guice-multibindings:4.0.0")
    }
    group(name: "test-compile") {
      dependency(id: "org.testng:testng:6.8.7")
    }
  }

  publications {
    standard()
  }
}

/*
 * Define Plugins
 */

dependency = loadPlugin(id: "org.savantbuild.plugin:dependency:${savantVersion}")
java = loadPlugin(id: "org.savantbuild.plugin:java:${savantVersion}")
javaTestNG = loadPlugin(id: "org.savantbuild.plugin:java-testng:${savantVersion}")
idea = loadPlugin(id: "org.savantbuild.plugin:idea:${savantVersion}")
release = loadPlugin(id: "org.savantbuild.plugin:release-git:${savantVersion}")

/*
 * Plugin Settings
 */

java.settings.javaVersion = "1.8"
javaTestNG.settings.javaVersion = "1.8"

target(name: "clean", description: "Cleans out the build directory") {
  java.clean()
}

target(name: "compile", description: "Compiles the project") {
  java.compile()
}

target(name: "jar", description: "JARs the project", dependsOn: ["compile"]) {
  java.jar()
}

target(name: "test", description: "Executes the projects tests", dependsOn: ["jar"]) {
  javaTestNG.test()
}

target(name: "int", description: "Releases a local integration build of the project", dependsOn: ["test"]) {
  dependency.integrate()
}

target(name: "idea", description: "Updates the IntelliJ IDEA module file") {
  idea.iml()
}

target(name: "print-dependency-tree", description: "Prints the dependency tree") {
  dependency.printFull()
}

target(name: "release", description: "Releases a full version of the project", dependsOn: ["clean", "int"]) {
  release.release()
}

This build file includes all the necessary dependencies that you will need to write your plugin.

Also, if you are using IntelliJ and the Savant build file above, you can execute this command to update the IntelliJ IML file to include the project’s dependencies:

$ sb idea

2.4. Create the Plugin Guice Module

Passport uses Guice for dependency injection and also to setup plugins. No matter what type of plugin you are writing, you need to add a single Guice module to your project.

In order for Passport to locate your plugin, the package you put your plugin module into must include a parent package named either plugin or plugins. For example, a plugin class cannot be named com.mycompany.MyPassportPluginModule. Instead, it must be named com.mycompany.plugins.MyPassportPluginModule.

Create a Guice module under the src/main/java directory. Here is an example, but you can change the directory names and file name to anything you want:

com/mycompany/passport/plugins/guice/MyPassportPluginModule.java

Here is an template Guice module that you will use for your plugin:

package com.mycompany.passport.plugins.guice;

import com.google.inject.AbstractModule;
import com.google.inject.multibindings.MapBinder;
import com.inversoft.passport.plugin.spi.PluginModule;

/**
 * My cool Passport plugin module.
 */
@PluginModule
public class MyCompanyPassportPluginsModule extends AbstractModule {
  @Override
  protected void configure() {
    // You Guice bindings will go here!
  }
}

Notice that this plugin is annotated with the class com.inversoft.passport.plugin.spi.PluginModule. This is how Passport locates the Guice module and installs your plugin.

2.5. Write your Plugin

Currently, Passport provides a single plugin point for password encryption schemes. This allows you to define a password encryption scheme that matches your current method so that imported users don’t need to reset their passwords. You can read more about implementing the password encryption plugin interface on the Password Encryptors page.

2.6. Install a Plugin

After you have completed your plugin code and all of your unit tests pass, you are ready to install the plugin into Passport. First, you need to create the plugin JAR file. If you are using our Savant build script from above, you will execute this command:

$ sb jar

This will produce the plugin JAR and place it in the build/jars directory.

Next, you need to create the plugin directory in your Passport installation. Depending on where you installed Passport, you will create the plugin directory in the INVERSOFT_HOME directory. This directory is the directory right above the PASSPORT_HOME directory. Here are some examples for the plugin directory:

Linux/Mac/Unix

/usr/local/inversoft/plugins/passport

Windows

C:\inversoft\plugins\passport

The location of this directory might be different if you install using the ZIP bundles and placed Passport somewhere else.

Next, you copy this JAR file from your plugin project into the plugin directory like this:

Linux/Mac/Unix
$ cp build/jars/test-plugin-0.1.0.jar /usr/local/inversoft/plugins/passport
Windows
C:\myplugin> cp build\jars\test-plugin-0.1.0.jar C:\inversoft\plugins\passport

Now you can restart Passport and it should load your plugin. If you plugin is found and loaded successfully, you should see a message like this in the logs:

INFO  com.inversoft.passport.api.plugin.guice.PluginModule - Installing plugin [com.mycompany.passport.plugins.guice.MyCompanyPassportPluginsModule]
INFO  com.inversoft.passport.api.plugin.guice.PluginModule - Plugin successfully installed