Browse Source

Merge remote-tracking branch 'origin/master' into css-tabs

jalbr74 7 years ago
parent
commit
e15159345c

+ 1 - 1
client/package.json

@@ -13,7 +13,7 @@
     "test": "karma start --NODE_ENV=test",
     "test-single-run": "karma start --NODE_ENV=test --singleRun --no-auto-watch",
     "start": "webpack-dev-server --config=webpack.dev.js --NODE_ENV=dev --colors",
-    "sync": "webpack --config=webpack.build.js --NODE_ENV=production --output-path=../server/src/main/webapp/public/resources/webjars/pwm-client --watch --colors"
+    "sync": "webpack --config=webpack.build.js --NODE_ENV=production --output-path=../server/target/pwm-1.8.0-SNAPSHOT/public/resources/webjars/pwm-client --watch --colors"
   },
   "author": "",
   "license": "ISC",

+ 50 - 0
client/src/pages/configeditor/configeditor.controller.ts

@@ -0,0 +1,50 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * 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 should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+import {element, ICompileService, IScope, ITemplateCacheService, module} from 'angular';
+
+/**
+ * Angular controller for the configeditor.jsp page.  This class is used to transition the page from JSPs into
+ * eventually a single page angular application.
+ */
+export default class ConfigEditorController {
+    static $inject = ['$scope', '$compile'];
+    constructor(
+        private $scope: IScope,
+        private $compile: ICompileService
+    ) {
+        $scope.$on('content-added', (event, elementId) => {
+            this.digestNewContent(elementId);
+        });
+    }
+
+    digestNewContent(elementId: string) {
+        if (elementId) {
+            const element = document.getElementById(elementId);
+
+            if (element) {
+                this.$compile(element)(this.$scope);
+                this.$scope.$digest();
+            }
+        }
+    }
+}

+ 27 - 0
client/src/pages/configeditor/configeditor.module.ts

@@ -0,0 +1,27 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * 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 should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+import { module } from 'angular';
+import ConfigEditorController from './configeditor.controller';
+
+module('configeditor.module', ['textAngular'])
+    .controller('ConfigEditorController', ConfigEditorController);

+ 2 - 1
client/webpack.build.js

@@ -29,7 +29,8 @@ module.exports = webpackMerge(commonConfig, {
     devtool: 'source-map',
     entry: {
         'peoplesearch.ng': './src/main',
-        'changepassword.ng': './src/pages/changepassword/changepassword.module'
+        'changepassword.ng': './src/pages/changepassword/changepassword.module',
+        'configeditor.ng': './src/pages/configeditor/configeditor.module'
     },
     module: {
         loaders: [

+ 2 - 1
client/webpack.dev.js

@@ -29,7 +29,8 @@ module.exports = webpackMerge(commonConfig, {
     devtool: 'cheap-module-source-map',
     entry: {
         'peoplesearch.ng': './src/main.dev',
-        'changepassword.ng': './src/pages/changepassword/changepassword.module'
+        'changepassword.ng': './src/pages/changepassword/changepassword.module',
+        'configeditor.ng': './src/pages/configeditor/configeditor.module'
     },
     module: {
         loaders: [

+ 46 - 0
onejar/onejar-assembly.xml

@@ -0,0 +1,46 @@
+<!--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2017 The PWM Project
+  ~
+  ~ 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 should have received a copy of the GNU General Public License
+  ~ along with this program; if not, write to the Free Software
+  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  -->
+
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+
+    <id>onejar</id>
+    <formats>
+        <format>jar</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <dependencySets>
+        <dependencySet>
+            <outputDirectory>/</outputDirectory>
+            <useProjectArtifact>true</useProjectArtifact>
+            <unpack>true</unpack>
+            <scope>runtime</scope>
+        </dependencySet>
+    </dependencySets>
+    <files>
+        <file>
+            <source>${project.basedir}${file.separator}..${file.separator}server${file.separator}target${file.separator}pwm-${project.version}.war</source>
+            <outputDirectory>/</outputDirectory>
+            <destName>embed.war</destName>
+        </file>
+    </files>
+</assembly>

+ 150 - 0
onejar/pom.xml

@@ -0,0 +1,150 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <parent>
+        <groupId>org.pwm-project</groupId>
+        <artifactId>pwm-parent</artifactId>
+        <version>1.8.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>pwm-onejar</artifactId>
+
+    <packaging>jar</packaging>
+
+    <name>PWM Password Self Service: Executable Jar</name>
+
+    <properties>
+        <tomcat.version>9.0.4</tomcat.version>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <version>3.0.0-M1</version>
+                <executions>
+                    <execution>
+                        <id>enforce-maven</id>
+                        <goals>
+                            <goal>enforce</goal>
+                        </goals>
+                        <configuration>
+                            <rules>
+                                <requireMavenVersion>
+                                    <version>${pwm.minimum.maven.version}</version>
+                                </requireMavenVersion>
+                            </rules>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <!-- prevent normal jar from being built -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>3.0.2</version>
+                <executions>
+                    <execution>
+                        <id>default-jar</id>
+                        <phase>never</phase>
+                        <configuration>
+                            <finalName>unwanted</finalName>
+                            <classifier>unwanted</classifier>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.7.0</version>
+                <configuration>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.1.0</version>
+
+                <configuration>
+                    <appendAssemblyId>false</appendAssemblyId>
+                    <descriptors>
+                        <descriptor>onejar-assembly.xml</descriptor>
+                    </descriptors>
+                    <archive>
+                        <manifestEntries>
+                            <Main-Class>password.pwm.TomcatOneJarMain</Main-Class>
+                            <Implementation-Title>${project.name}</Implementation-Title>
+                            <Implementation-Version>${project.version}</Implementation-Version>
+                            <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+                            <Implementation-URL>${project.organization.url}</Implementation-URL>
+                            <Implementation-Build>${build.number}</Implementation-Build>
+                            <Implementation-Revision>${build.revision}</Implementation-Revision>
+                            <Implementation-Version-Display>v${project.version} b${build.number} r${build.revision}</Implementation-Version-Display>
+                        </manifestEntries>
+                    </archive>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>assemble-all</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+
+        <!--
+        This is included via the assembly plugin descriptor, so its not really required here but keeps
+        the module build order correct.
+        -->
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>pwm</artifactId>
+            <version>${project.version}</version>
+            <type>war</type>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- embedded tomcat -->
+        <dependency>
+            <groupId>org.apache.tomcat</groupId>
+            <artifactId>tomcat-catalina</artifactId>
+            <version>${tomcat.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat</groupId>
+            <artifactId>tomcat-util</artifactId>
+            <version>${tomcat.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat.embed</groupId>
+            <artifactId>tomcat-embed-core</artifactId>
+            <version>${tomcat.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat.embed</groupId>
+            <artifactId>tomcat-embed-jasper</artifactId>
+            <version>${tomcat.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>commons-cli</groupId>
+            <artifactId>commons-cli</artifactId>
+            <version>1.4</version>
+        </dependency>
+    </dependencies>
+</project>

+ 62 - 0
onejar/src/main/java/password/pwm/TomcatConfig.java

@@ -0,0 +1,62 @@
+package password.pwm;
+
+import java.io.File;
+import java.io.InputStream;
+
+class TomcatConfig {
+    private int port;
+    private File applicationPath;
+    private File workingPath;
+    private InputStream war;
+    private String context;
+
+    public int getPort( )
+    {
+        return port;
+    }
+
+    public void setPort( final int port )
+    {
+        this.port = port;
+    }
+
+    public File getApplicationPath( )
+    {
+        return applicationPath;
+    }
+
+    public void setApplicationPath( final File applicationPath )
+    {
+        this.applicationPath = applicationPath;
+    }
+
+    public File getWorkingPath( )
+    {
+        return workingPath;
+    }
+
+    public void setWorkingPath( final File workingPath )
+    {
+        this.workingPath = workingPath;
+    }
+
+    public InputStream getWar( )
+    {
+        return war;
+    }
+
+    public void setWar( final InputStream war )
+    {
+        this.war = war;
+    }
+
+    public String getContext( )
+    {
+        return context;
+    }
+
+    public void setContext( final String context )
+    {
+        this.context = context;
+    }
+}

+ 335 - 0
onejar/src/main/java/password/pwm/TomcatOneJarMain.java

@@ -0,0 +1,335 @@
+package password.pwm;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.connector.CoyoteAdapter;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+import javax.servlet.ServletException;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.net.URL;
+import java.nio.file.FileVisitOption;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.logging.Logger;
+
+public class TomcatOneJarMain
+{
+    private static final  Logger LOGGER = Logger.getLogger(TomcatOneJarMain.class.getName());
+
+    private static final String ARG_APP_PATH = "applicationPath";
+    private static final String ARG_WORK_PATH = "workPath";
+    private static final String ARG_VERSION = "version";
+    private static final String ARG_HELP = "help";
+    private static final String ARG_WARFILE = "war";
+    private static final String ARG_PORT = "port";
+    private static final String ARG_CONTEXT = "context";
+
+    private static final String DEFAULT_CONTEXT = "pwm";
+    private static final int DEFAULT_PORT = 8080;
+    private static final String DEFAULT_WORK_DIR_NAME = ".pwm-workpath";
+
+    private static final String EMBED_WAR_NAME = "embed.war";
+
+    public static void main(final String[] args) throws ServletException, IOException, LifecycleException
+    {
+        if (args == null || args.length == 0) {
+            outputHelp();
+        } else
+        {
+            final CommandLine commandLine = parseOptions( args );
+            if ( commandLine.hasOption( ARG_VERSION ) ) {
+                System.out.println( getVersion() );
+            } else if ( commandLine.hasOption( ARG_HELP ) ) {
+                outputHelp();
+            } else {
+                final TomcatConfig tomcatConfig = makeTomcatConfig( commandLine );
+                startTomcat(tomcatConfig);
+            }
+        }
+    }
+
+    private static TomcatConfig makeTomcatConfig(final CommandLine commandLine) throws IOException
+    {
+        final TomcatConfig tomcatConfig = new TomcatConfig();
+        tomcatConfig.setApplicationPath( parseFileOption( commandLine, ARG_APP_PATH ) );
+
+        if (commandLine.hasOption( ARG_CONTEXT )) {
+            tomcatConfig.setContext( commandLine.getOptionValue( ARG_CONTEXT ) );
+        } else {
+            tomcatConfig.setContext( DEFAULT_CONTEXT );
+        }
+
+        if (commandLine.hasOption( ARG_WARFILE )) {
+            final File inputWarFile = new File (commandLine.getOptionValue( ARG_WARFILE ));
+            if (!inputWarFile.exists()) {
+                System.out.println( "output war file " + inputWarFile.getAbsolutePath() + "does not exist" );
+                System.exit( -1 );
+                return null;
+            }
+            tomcatConfig.setWar( new FileInputStream( inputWarFile ) );
+        } else {
+            tomcatConfig.setWar( getEmbeddedWar() );
+        }
+
+        tomcatConfig.setPort( DEFAULT_PORT );
+        if (commandLine.hasOption( ARG_PORT )) {
+            try {
+                tomcatConfig.setPort( Integer.parseInt( commandLine.getOptionValue( ARG_PORT ) ) );
+            } catch (NumberFormatException e) {
+                System.out.println( ARG_PORT + " argument must be numeric" );
+                System.exit( -1 );
+            }
+        }
+
+        if ( checkIfPortInUse( tomcatConfig.getPort())) {
+            System.out.println( "port " + tomcatConfig.getPort() +  " is in use" );
+            System.exit( -1 );
+        }
+
+        if (commandLine.hasOption( ARG_WORK_PATH )) {
+            tomcatConfig.setWorkingPath( parseFileOption( commandLine, ARG_WORK_PATH ) );
+        } else {
+            tomcatConfig.setWorkingPath( figureDefaultWorkPath(tomcatConfig) );
+        }
+
+        return tomcatConfig;
+    }
+
+    private static File parseFileOption(final  CommandLine commandLine, final String argName) {
+        if (!commandLine.hasOption( argName )) {
+            outAndExit(  "option " + argName + " required");
+        }
+        final File file = new File(commandLine.getOptionValue( argName ));
+        if (!file.isAbsolute()) {
+            outAndExit( "a fully qualified file path name is required for " + argName);
+        }
+        if (!file.exists()) {
+            outAndExit( "path specified by " + argName + " must exist");
+        }
+        return file;
+    }
+
+    private static void outputHelp() throws IOException
+    {
+        final HelpFormatter formatter = new HelpFormatter();
+        System.out.println( getVersion() );
+        System.out.println( "usage:" );
+        formatter.printOptions( System.console().writer(), HelpFormatter.DEFAULT_WIDTH, makeOptions(),3 , 8);
+    }
+
+    private static void startTomcat( final TomcatConfig tomcatConfig) throws ServletException, IOException
+    {
+        purgeDirectory( tomcatConfig.getWorkingPath().toPath() );
+        {
+            final Path outputPath = tomcatConfig.getWorkingPath().toPath().resolve( EMBED_WAR_NAME );
+            final InputStream warSource = tomcatConfig.getWar();
+            Files.copy(warSource, outputPath);
+        }
+        System.setProperty( "PWM_APPLICATIONPATH", tomcatConfig.getApplicationPath().getAbsolutePath() );
+
+        final Tomcat tomcat = new Tomcat();
+
+        {
+            final File basePath = new File( tomcatConfig.getWorkingPath().getPath() + File.separator + "b" );
+            basePath.mkdir();
+            tomcat.setBaseDir( basePath.getAbsolutePath() );
+        }
+        {
+            final File basePath = new File( tomcatConfig.getWorkingPath().getPath() + File.separator + "a" );
+            basePath.mkdir();
+            tomcat.getServer().setCatalinaBase( basePath );
+            tomcat.getServer().setCatalinaHome( basePath );
+        }
+        {
+            final File workPath = new File( tomcatConfig.getWorkingPath().getPath() + File.separator + "w" );
+            workPath.mkdir();
+            tomcat.getHost().setAppBase( workPath.getAbsolutePath() );
+        }
+
+        tomcat.setConnector( makeConnector( tomcatConfig ) );
+
+        tomcat.setPort( DEFAULT_PORT );
+        tomcat.getConnector();
+
+        tomcat.getHost().setAutoDeploy(false);
+        tomcat.getHost().setDeployOnStartup(false);
+
+        final String warPath = tomcatConfig.getWorkingPath().getAbsolutePath() + File.separator + EMBED_WAR_NAME;
+        final Context pwmContext = tomcat.addWebapp( "/" + tomcatConfig.getContext(), warPath );
+        LOGGER.info("Deployed " + pwmContext.getBaseName() + " as " + pwmContext.getBaseName());
+
+        try {
+            tomcat.start();
+        } catch (LifecycleException e) {
+            outAndExit( "unable to start tomcat: " + e.getMessage() );
+        }
+        LOGGER.info("tomcat started on " + tomcat.getHost());
+
+        tomcat.getServer().await();
+    }
+
+    private static Connector makeConnector(final TomcatConfig tomcatConfig) {
+        final Connector connector = new Connector("HTTP/1.1");
+        connector.setPort(tomcatConfig.getPort());
+        return connector;
+    }
+
+    private static CommandLine parseOptions(final String[] args) {
+        final CommandLineParser parser = new DefaultParser();
+        try {
+            return parser.parse( makeOptions(), args);
+        } catch ( ParseException e ) {
+            outAndExit(  "error parsing commandline: " + e.getMessage() );
+        }
+        return null;
+    }
+
+    private static Options makeOptions() {
+        final Map<String,Option> optionMap = new TreeMap<>(  );
+
+        optionMap.put(ARG_APP_PATH,Option.builder(ARG_APP_PATH)
+                .desc( "application path (required)" )
+                .numberOfArgs( 1 )
+                .required(false)
+                .build());
+
+        optionMap.put(ARG_WORK_PATH,Option.builder(ARG_WORK_PATH)
+                .desc( "temporary work path" )
+                .numberOfArgs( 1 )
+                .required(false)
+                .build());
+
+        optionMap.put(ARG_VERSION,Option.builder(ARG_VERSION)
+                .desc( "show version" )
+                .numberOfArgs( 0 )
+                .required(false)
+                .build());
+
+        optionMap.put(ARG_PORT,Option.builder()
+                .longOpt( ARG_PORT )
+                .desc( "web server port (default " + DEFAULT_PORT + ")" )
+                .numberOfArgs( 1 )
+                .build() );
+
+        optionMap.put(ARG_CONTEXT,Option.builder()
+                .longOpt( ARG_CONTEXT )
+                .desc( "context (url path) name (default " + DEFAULT_CONTEXT + ")" )
+                .numberOfArgs( 1 )
+                .build() );
+
+        optionMap.put(ARG_HELP,Option.builder(ARG_HELP)
+                .desc( "show this help" )
+                .numberOfArgs( 0 )
+                .required(false)
+                .build());
+
+        optionMap.put(ARG_WARFILE,Option.builder(ARG_WARFILE)
+                .desc( "source war file (default embedded)" )
+                .numberOfArgs( 1 )
+                .required(false)
+                .build());
+
+        final Options options = new Options();
+        optionMap.values().forEach( options::addOption );
+        return options;
+    }
+
+    private static InputStream getEmbeddedWar() throws IOException
+    {
+        final Class clazz = TomcatOneJarMain.class;
+        final String className = clazz.getSimpleName() + ".class";
+        final String classPath = clazz.getResource(className).toString();
+        if (!classPath.startsWith("jar")) {
+            outAndExit("not running from war, war option must be specified");
+            return null;
+        }
+        final String warPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) +
+                "/" + EMBED_WAR_NAME;
+        return new URL(warPath).openStream();
+    }
+
+    private static String getVersion() throws IOException
+    {
+        final Class clazz = TomcatOneJarMain.class;
+        final String className = clazz.getSimpleName() + ".class";
+        final String classPath = clazz.getResource(className).toString();
+        if (!classPath.startsWith("jar")) {
+            // Class not from JAR
+            return "--version missing--";
+        }
+        final String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) +
+                "/META-INF/MANIFEST.MF";
+        final Manifest manifest = new Manifest(new URL(manifestPath).openStream());
+        final Attributes attr = manifest.getMainAttributes();
+        return attr.getValue("Implementation-Version-Display")
+                + ", " + ServerInfo.getServerInfo();
+    }
+
+    private static void purgeDirectory(final Path rootPath)
+            throws IOException
+    {
+        System.out.println("purging work directory: " + rootPath);
+        Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS)
+                .sorted( Comparator.reverseOrder())
+                .map(Path::toFile)
+                .filter( file -> !rootPath.toString().equals( file.getPath() ) )
+                .forEach(File::delete);
+    }
+
+    private static boolean checkIfPortInUse( final int portNumber) {
+        boolean result;
+
+        try {
+            Socket s = new Socket("localhost", portNumber);
+            s.close();
+            result = true;
+        } catch(IOException e) {
+            result = false;
+        }
+
+        return(result);
+    }
+
+    private static File figureDefaultWorkPath(final TomcatConfig tomcatConfig) {
+        final String userHomePath = System.getProperty( "user.home" );
+        if (userHomePath != null && !userHomePath.isEmpty()) {
+            final File basePath = new File(userHomePath + File.separator
+                    + "." + DEFAULT_CONTEXT);
+            basePath.mkdir();
+            final File workPath = new File( basePath.getPath() + File.separator
+                    + "work-" + tomcatConfig.getContext() + "-" + Integer.toString( tomcatConfig.getPort() ));
+            workPath.mkdir();
+            System.out.println( "using work directory: " + workPath.getAbsolutePath() );
+            return workPath;
+        }
+
+        System.out.println( "cant locate user home directory" );
+        System.exit( -1 );
+        return null;
+    }
+
+    private static Object outAndExit(final String output) {
+        System.out.println(output);
+        System.exit( -1 );
+        return null;
+    }
+}

+ 1 - 0
pom.xml

@@ -32,6 +32,7 @@
     <modules>
         <module>client</module>
         <module>server</module>
+        <module>onejar</module>
     </modules>
 
     <build>

+ 11 - 0
server/pom.xml

@@ -848,6 +848,17 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>org.webjars.bower</groupId>
+            <artifactId>textAngular</artifactId>
+            <version>1.5.16</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.webjars.bower</groupId>
+                    <artifactId>angular</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
     </dependencies>
 
     <repositories>

+ 20 - 1
server/src/main/webapp/WEB-INF/jsp/configeditor.jsp

@@ -66,7 +66,7 @@
             </div>
         </div>
     </div>
-    <div id="centerbody-config" class="centerbody-config">
+    <div id="centerbody-config" class="centerbody-config" ng-app="configeditor.module" ng-controller="ConfigEditorController as $ctrl">
         <div id="settingSearchPanel">
             <table class="noborder settingSearchPanelTable">
                 <colgroup>
@@ -161,6 +161,25 @@
 <pwm:script-ref url="/public/resources/js/configeditor-settings-customlink.js"/>
 <pwm:script-ref url="/public/resources/js/configeditor-settings-remotewebservices.js"/>
 <pwm:script-ref url="/public/resources/js/admin.js"/>
+
+<%--Add support for angular--%>
+<pwm:script-ref url="/public/resources/webjars/angular/angular.min.js" />
+<pwm:script-ref url="/public/resources/webjars/angular-ui-router/release/angular-ui-router.min.js" />
+<pwm:script-ref url="/public/resources/webjars/angular-translate/dist/angular-translate.min.js" />
+<%--/ Add support for angular--%>
+
+<%--Add support for the "textAngular" library (a control for editing HTML)--%>
+<link rel="stylesheet" type="text/css" href="<pwm:url url='/public/resources/webjars/textAngular/dist/textAngular.css' addContext="true"/>"/>
+<pwm:script-ref url="/public/resources/webjars/textAngular/dist/textAngular-rangy.min.js" />
+<pwm:script-ref url="/public/resources/webjars/textAngular/dist/textAngular-sanitize.min.js" />
+<pwm:script-ref url="/public/resources/webjars/textAngular/dist/textAngular.min.js" />
+<%--/ Add support for the "textAngular" library (a control for editing HTML)--%>
+
+<%--Provide the angular code we made specifically for this page:--%>
+<link rel="stylesheet" type="text/css" href="<pwm:url url='/public/resources/html-editor.css' addContext="true"/>"/>
+<pwm:script-ref url="/public/resources/webjars/pwm-client/configeditor.ng.js" />
+<%--/ Provide the angular code we made specifically for this page:--%>
+
 <%@ include file="fragment/footer.jsp" %>
 </body>
 </html>

+ 85 - 0
server/src/main/webapp/public/resources/html-editor.css

@@ -0,0 +1,85 @@
+.html-editor {
+    height: 350px;
+}
+
+.html-editor .ta-editor {
+    border: 1px solid #EAEAEA;
+    height: 300px;
+    margin-top: 10px;
+    overflow: auto;
+    width: 790px;
+}
+
+.html-editor .btn-group {
+    display: inline-block;
+    margin-right: 5px;
+}
+
+.html-editor .btn-group .btn {
+    background: #eaeaea;
+    border-radius: 0;
+    border: 1px solid #ccc;
+    border-right: 0;
+    color: #434c50;
+    display: inline-block;
+    font-size: 14px;
+    margin: 0;
+    padding: 0px;
+    vertical-align: bottom;
+}
+
+.html-editor .btn-group .btn.active {
+    background-color: #a1a6a8;
+    color: #f6f9f8;
+}
+
+.html-editor .btn-group .btn:hover:not(.active) {
+    background-color: #f6f9f8;
+    border-color: #01a9e7;
+    color: #007cd0;
+}
+
+.html-editor .btn-group .btn:hover:not(.active) + .btn {
+    border-left-color: #01a9e7;
+}
+
+.html-editor .btn-group button {
+    height: 24px;
+    width: 24px;
+}
+
+.html-editor .btn-group .btn:first-of-type {
+    border-top-left-radius: 3px;
+    border-bottom-left-radius: 3px;
+}
+
+.html-editor .btn-group .btn:last-of-type {
+    border-right: 1px solid #ccc;
+    border-top-right-radius: 3px;
+    border-bottom-right-radius: 3px;
+}
+
+.html-editor .btn-group .btn:hover:last-of-type {
+    border-right-color: #01a9e7;
+}
+
+.html-editor.focussed .ta-scroll-window.form-control {
+    border-color: #01a9e7;
+    box-shadow: none;
+}
+
+.html-editor .ta-scroll-window > .ta-bind {
+    min-height: 285px;
+}
+
+.html-editor.focussed .ta-scroll-window > .ta-bind {
+    outline: none;
+}
+
+.ta-editor p {
+    max-width: none;
+    position: static;
+    margin-left: 0;
+    margin-right: 0;
+    font-size: 14px;
+}

+ 26 - 32
server/src/main/webapp/public/resources/js/configeditor-settings.js

@@ -1923,39 +1923,33 @@ EmailTableHandler.instrumentRow = function(settingKey, localeName) {
 
 
 EmailTableHandler.htmlBodyEditor = function(keyName, localeName) {
-    require(["dijit/Editor","dijit/_editor/plugins/AlwaysShowToolbar","dijit/_editor/plugins/LinkDialog","dijit/_editor/plugins/ViewSource","dijit/_editor/plugins/FontChoice","dijit/_editor/plugins/TextColor"],
-        function(Editor,AlwaysShowToolbar){
-            var idValue = keyName + "_" + localeName + "_htmlEditor";
-            var bodyText = '';
-            bodyText += '<div id="' + idValue + '" style="border:2px solid #EAEAEA; height:300px"></div>';
-            PWM_MAIN.showDialog({
-                title: "HTML Editor",
-                text: bodyText,
-                showClose:true,
-                showCancel:true,
-                dialogClass: 'wide',
-                loadFunction:function(){
-                    PWM_MAIN.clearDijitWidget(idValue);
-                    new Editor({
-                        extraPlugins: [
-                            AlwaysShowToolbar,"viewsource",
-                            {name:"dijit/_editor/plugins/LinkDialog",command:"createLink",urlRegExp:".*"},
-                            "fontName","foreColor"
-                        ],
-                        height: '300px',
-                        value: PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'],
-                        style: '',
-                        onChange: function(){PWM_VAR['temp-dialogInputValue'] = this.get('value')},
-                        onKeyUp: function(){PWM_VAR['temp-dialogInputValue'] = this.get('value')}
-                    },idValue).startup();
-                },
-                okAction:function(){
-                    PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] = PWM_VAR['temp-dialogInputValue'];
-                    EmailTableHandler.writeSetting(keyName,true);
-                }
-            });
+    // Grab the scope from the angular controller we created on the div element with ID: centerbody-config
+    var $scope = angular.element(document.getElementById("centerbody-config")).scope();
+    var idValue = keyName + "_" + localeName + "_htmlEditor";
+    var toolbarButtons =
+        "[" +
+        "['h1','h2','h3','h4','h5','h6','p','pre','quote']," +
+        "['bold','italics','underline','strikeThrough','ul','ol','undo','redo','clear']," +
+        "['justifyLeft','justifyCenter','justifyRight','justifyFull','indent','outdent']," +
+        "['html','insertImage','insertLink','insertVideo']" +
+        "]";
+
+    PWM_MAIN.showDialog({
+        title: "HTML Editor",
+        text: '<div id="' + idValue + '" text-angular ng-model="htmlText" ta-toolbar="' + toolbarButtons + '" class="html-editor"></div>',
+        showClose:true,
+        showCancel:true,
+        dialogClass: 'wide',
+        loadFunction: function(){
+            // Put the existing value into the scope, and tell the controller to process the element with ID: idValue
+            $scope.htmlText =  PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'];
+            $scope.$broadcast("content-added", idValue);
+        },
+        okAction:function(){
+            PWM_VAR['clientSettingCache'][keyName][localeName]['bodyHtml'] = $scope.htmlText;
+            EmailTableHandler.writeSetting(keyName,true);
         }
-    );
+    });
 };