ソースを参照

Fixed conflicts with master

rkeil 7 年 前
コミット
c8b1ccbbe1
28 ファイル変更1085 行追加310 行削除
  1. 1 1
      client/package.json
  2. 50 0
      client/src/pages/configeditor/configeditor.controller.ts
  3. 27 0
      client/src/pages/configeditor/configeditor.module.ts
  4. 2 1
      client/webpack.build.js
  5. 2 1
      client/webpack.dev.js
  6. 46 0
      onejar/onejar-assembly.xml
  7. 150 0
      onejar/pom.xml
  8. 62 0
      onejar/src/main/java/password/pwm/TomcatConfig.java
  9. 335 0
      onejar/src/main/java/password/pwm/TomcatOneJarMain.java
  10. 1 0
      pom.xml
  11. 34 25
      server/pom.xml
  12. 7 1
      server/src/build/checkstyle-import.xml
  13. 1 0
      server/src/main/java/password/pwm/AppProperty.java
  14. 2 2
      server/src/main/java/password/pwm/bean/TelemetryPublishBean.java
  15. 1 1
      server/src/main/java/password/pwm/http/PwmResponse.java
  16. 9 43
      server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java
  17. 21 148
      server/src/main/java/password/pwm/http/filter/GZIPFilter.java
  18. 62 49
      server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java
  19. 53 0
      server/src/main/java/password/pwm/ldap/PwmLdapVendor.java
  20. 4 2
      server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java
  21. 65 0
      server/src/main/java/password/pwm/svc/sessiontrack/UserAgentUtils.java
  22. 5 2
      server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java
  23. 11 0
      server/src/main/java/password/pwm/util/java/JsonUtil.java
  24. 2 1
      server/src/main/java/password/pwm/ws/server/RestServlet.java
  25. 1 0
      server/src/main/resources/password/pwm/AppProperty.properties
  26. 20 1
      server/src/main/webapp/WEB-INF/jsp/configeditor.jsp
  27. 85 0
      server/src/main/webapp/public/resources/html-editor.css
  28. 26 32
      server/src/main/webapp/public/resources/js/configeditor-settings.js

+ 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>

+ 34 - 25
server/pom.xml

@@ -92,7 +92,7 @@
             <plugin>
                 <groupId>com.github.spotbugs</groupId>
                 <artifactId>spotbugs-maven-plugin</artifactId>
-                <version>3.1.0-RC8</version>
+                <version>3.1.1</version>
                 <dependencies>
                     <dependency>
                         <groupId>com.github.spotbugs</groupId>
@@ -327,32 +327,25 @@
                 </executions>
             </plugin>
             <plugin>
+                <!-- verifies jsp pages compile, but does not include the compiled versions in the WAR -->
                 <groupId>io.leonard.maven.plugins</groupId>
                 <artifactId>jspc-maven-plugin</artifactId>
                 <version>2.4.2</version>
-            </plugin>
-            <plugin>
-                <artifactId>maven-clean-plugin</artifactId>
-                <version>3.0.0</version>
                 <executions>
                     <execution>
-                        <id>remove-compiled-jsps</id>
-                        <phase>prepare-package</phase>
                         <goals>
-                            <goal>clean</goal>
+                            <goal>compile</goal>
                         </goals>
+                        <phase>compile</phase>
                         <configuration>
-                            <excludeDefaultDirectories>true</excludeDefaultDirectories>
-                            <filesets>
-                                <fileset>
-                                    <directory>target/classes/jsp</directory>
-                                </fileset>
-                            </filesets>
+                            <compilerVersion>${maven.compiler.source}</compilerVersion>
+                            <keepSources>false</keepSources>
                         </configuration>
                     </execution>
                 </executions>
             </plugin>
             <plugin>
+                <!-- builds xml file of dependencies and licenses for use in about page -->
                 <groupId>com.github.jinnovations</groupId>
                 <artifactId>attribution-maven-plugin</artifactId>
                 <version>0.9.5</version>
@@ -452,7 +445,7 @@
             <plugin> <!-- checks owsp vulnerability database during verify phase -->
                 <groupId>org.owasp</groupId>
                 <artifactId>dependency-check-maven</artifactId>
-                <version>3.0.1</version>
+                <version>3.1.1</version>
                 <configuration>
                     <nuspecAnalyzerEnabled>false</nuspecAnalyzerEnabled>
                     <assemblyAnalyzerEnabled>false</assemblyAnalyzerEnabled>
@@ -584,19 +577,19 @@
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
-            <version>2.2.11</version>
+            <version>2.13.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.assertj</groupId>
             <artifactId>assertj-core</artifactId>
-            <version>3.5.2</version>
+            <version>3.9.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>com.github.tomakehurst</groupId>
             <artifactId>wiremock</artifactId>
-            <version>1.58</version>
+            <version>2.14.0</version>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -610,13 +603,13 @@
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
-            <version>3.0.1</version>
+            <version>4.0.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>javax.servlet.jsp</groupId>
             <artifactId>jsp-api</artifactId>
-            <version>2.2</version>
+            <version>2.2.1-b03</version>
             <scope>provided</scope>
         </dependency>
         <!-- / container dependencies -->
@@ -687,7 +680,7 @@
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
-            <version>4.5.4</version>
+            <version>4.5.5</version>
         </dependency>
         <dependency>
             <groupId>org.graylog2</groupId>
@@ -707,7 +700,7 @@
         <dependency>
             <groupId>org.jasig.cas.client</groupId>
             <artifactId>cas-client-core</artifactId>
-            <version>3.4.1</version>
+            <version>3.5.0</version>
         </dependency>
         <dependency>
             <groupId>net.glxn</groupId>
@@ -755,9 +748,9 @@
             <version>2.8.2</version>
         </dependency>
         <dependency>
-            <groupId>eu.bitwalker</groupId>
-            <artifactId>UserAgentUtils</artifactId>
-            <version>1.20</version>
+            <groupId>com.blueconic</groupId>
+            <artifactId>browscap-java</artifactId>
+            <version>1.2.1</version>
         </dependency>
         <dependency>
             <groupId>org.jetbrains.xodus</groupId>
@@ -784,6 +777,11 @@
             <artifactId>zxcvbn</artifactId>
             <version>1.2.3</version>
         </dependency>
+        <dependency>
+            <groupId>com.github.ziplet</groupId>
+            <artifactId>ziplet</artifactId>
+            <version>2.3.0</version>
+        </dependency>
 
 
 
@@ -850,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>

+ 7 - 1
server/src/build/checkstyle-import.xml

@@ -70,7 +70,6 @@
     <allow pkg="javax.annotation"/>
     <allow pkg="java.awt"/>
     <allow pkg="javax.security"/>
-    <allow pkg="eu.bitwalker.useragentutils"/>
     <allow pkg="javax.servlet"/>
     <allow pkg="javax.net"/>
     <allow pkg="javax.crypto"/>
@@ -133,5 +132,12 @@
         <allow pkg="org.apache.http"/>
     </subpackage>
 
+    <subpackage name="http.filter">
+        <allow pkg="com.github.ziplet.filter.compression"/>
+    </subpackage>
+
+    <subpackage name="svc.sessiontrack">
+        <allow pkg="com.blueconic.browscap"/>
+    </subpackage>
 
 </import-control>

+ 1 - 0
server/src/main/java/password/pwm/AppProperty.java

@@ -65,6 +65,7 @@ public enum AppProperty {
     CONFIG_NEWUSER_PASSWORD_POLICY_CACHE_MS         ("config.newuser.passwordPolicyCacheMS"),
     CONFIG_THEME                                    ("config.theme"),
     CONFIG_JBCRYPT_PWLIB_ENABLE                     ("config.enableJbCryptPwLibrary"),
+    CONFIG_EDITOR_BLOCK_OLD_IE                      ("configEditor.blockOldIE"),
     CONFIG_EDITOR_QUERY_FILTER_TEST_LIMIT           ("configEditor.queryFilter.testLimit"),
     CONFIG_EDITOR_IDLE_TIMEOUT                      ("configEditor.idleTimeoutSeconds"),
     CONFIG_GUIDE_IDLE_TIMEOUT                       ("configGuide.idleTimeoutSeconds"),

+ 2 - 2
server/src/main/java/password/pwm/bean/TelemetryPublishBean.java

@@ -22,9 +22,9 @@
 
 package password.pwm.bean;
 
-import com.novell.ldapchai.provider.DirectoryVendor;
 import lombok.Builder;
 import lombok.Getter;
+import password.pwm.ldap.PwmLdapVendor;
 
 import java.io.Serializable;
 import java.time.Instant;
@@ -39,7 +39,7 @@ public class TelemetryPublishBean implements Serializable {
     private final String instanceHash;
     private final String siteDescription;
     private final Instant installTime;
-    private final List<DirectoryVendor> ldapVendor;
+    private final List<PwmLdapVendor> ldapVendor;
     private final Map<String,String> statistics;
     private final List<String> configuredSettings;
     private final String versionBuild;

+ 1 - 1
server/src/main/java/password/pwm/http/PwmResponse.java

@@ -235,7 +235,7 @@ public class PwmResponse extends PwmHttpResponseWrapper {
         final HttpServletResponse resp = pwmRequest.getPwmResponse().getHttpServletResponse();
         resp.setStatus(redirectType.getCode()); // http "other" redirect
         resp.setHeader(HttpHeader.Location.getHttpName(), url);
-        LOGGER.trace("sending " + redirectType.getCode() + " redirect to " + url);
+        LOGGER.trace(pwmRequest, "sending " + redirectType.getCode() + " redirect to " + url);
     }
 
     private void preCommitActions() {

+ 9 - 43
server/src/main/java/password/pwm/http/filter/ConfigAccessFilter.java

@@ -22,8 +22,6 @@
 
 package password.pwm.http.filter;
 
-import eu.bitwalker.useragentutils.Browser;
-import eu.bitwalker.useragentutils.UserAgent;
 import password.pwm.AppProperty;
 import password.pwm.Permission;
 import password.pwm.PwmApplication;
@@ -39,7 +37,6 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
-import password.pwm.http.HttpHeader;
 import password.pwm.http.JspUrl;
 import password.pwm.http.ProcessStatus;
 import password.pwm.http.PwmRequest;
@@ -48,6 +45,7 @@ import password.pwm.http.PwmSession;
 import password.pwm.http.PwmURL;
 import password.pwm.http.bean.ConfigManagerBean;
 import password.pwm.svc.intruder.RecordType;
+import password.pwm.svc.sessiontrack.UserAgentUtils;
 import password.pwm.util.java.JavaHelper;
 import password.pwm.util.java.JsonUtil;
 import password.pwm.util.java.TimeDuration;
@@ -76,11 +74,14 @@ public class ConfigAccessFilter extends AbstractPwmFilter {
             return;
         }
 
-        try {
-            checkUserAgent(pwmRequest);
-        } catch (PwmException e) {
-            pwmRequest.respondWithError(e.getErrorInformation());
-            return;
+        final boolean blockOldIE = Boolean.parseBoolean( pwmRequest.getPwmApplication().getConfig().readAppProperty( AppProperty.CONFIG_EDITOR_BLOCK_OLD_IE ) );
+        if (blockOldIE) {
+            try {
+                UserAgentUtils.checkIfPreIE11( pwmRequest );
+            } catch ( PwmException e ) {
+                pwmRequest.respondWithError( e.getErrorInformation() );
+                return;
+            }
         }
 
         final ConfigManagerBean configManagerBean = pwmRequest.getPwmApplication().getSessionStateService().getBean(pwmRequest, ConfigManagerBean.class);
@@ -359,41 +360,6 @@ public class ConfigAccessFilter extends AbstractPwmFilter {
         return Integer.parseInt(pwmRequest.getConfig().readAppProperty(AppProperty.CONFIG_MAX_PERSISTENT_LOGIN_SECONDS));
     }
 
-    private void checkUserAgent(final PwmRequest pwmRequest) throws PwmUnrecoverableException {
-        final String userAgentString = pwmRequest.readHeaderValueAsString(HttpHeader.UserAgent);
-        if (userAgentString == null || userAgentString.isEmpty()) {
-            return;
-        }
-
-        boolean badBrowser = false;
-        try {
-            final UserAgent userAgent = new UserAgent(userAgentString);
-            final Browser browser = userAgent.getBrowser();
-            switch (browser) {
-                case IE5:
-                case IE5_5:
-                case IE6:
-                case IE7:
-                case IE8:
-                case IE9:
-                case IE10:
-                    badBrowser = true;
-                    break;
-
-                default:
-                    //other browsers okay
-                    break;
-
-            }
-        } catch (Exception e) {
-            LOGGER.error(pwmRequest, "error during browser user-agent detection: " + e.getMessage());
-        }
-
-        if (badBrowser) {
-            final String errorMsg = "Internet Explorer version is not supported for this function.  Please use Internet Explorer 11 or higher or another web browser.";
-            throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNAUTHORIZED, errorMsg));
-        }
-    }
 
     private static ProcessStatus denyAndError(final PwmRequest pwmRequest, final ErrorInformation errorInformation)
             throws ServletException, PwmUnrecoverableException, IOException

+ 21 - 148
server/src/main/java/password/pwm/http/filter/GZIPFilter.java

@@ -22,11 +22,11 @@
 
 package password.pwm.http.filter;
 
+import com.github.ziplet.filter.compression.CompressingFilter;
 import password.pwm.AppProperty;
 import password.pwm.PwmApplication;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.http.ContextManager;
-import password.pwm.http.HttpHeader;
 import password.pwm.http.PwmURL;
 import password.pwm.util.logging.PwmLogger;
 
@@ -34,17 +34,10 @@ import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
 import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.zip.GZIPOutputStream;
 
 /**
  * GZip Filter Wrapper.  This filter must be invoked _before_ a PwmRequest object is instantiated, else
@@ -53,171 +46,51 @@ import java.util.zip.GZIPOutputStream;
 public class GZIPFilter implements Filter {
     private static final PwmLogger LOGGER = PwmLogger.forClass(GZIPFilter.class);
 
+    private final CompressingFilter compressingFilter = new CompressingFilter();
+    private boolean enabled = false;
+
     public void init(final FilterConfig filterConfig)
             throws ServletException
     {
+        final PwmApplication pwmApplication;
+        try {
+            pwmApplication = ContextManager.getPwmApplication( filterConfig.getServletContext() );
+            enabled = Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_ENABLE_GZIP));
+        } catch (PwmUnrecoverableException e) {
+            LOGGER.warn( "unable to load application configuration, defaulting to disabled" );
+        }
+
+        compressingFilter.init( filterConfig );
     }
 
     public void destroy()
     {
+        compressingFilter.destroy();
     }
 
     @Override
     public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain)
             throws IOException, ServletException
     {
-        final String acceptEncoding = ((HttpServletRequest)servletRequest).getHeader(HttpHeader.Accept_Encoding.getHttpName());
-        if (acceptEncoding != null && acceptEncoding.contains("gzip") && isEnabled(servletRequest)) {
-            final GZIPHttpServletResponseWrapper gzipResponse = new GZIPHttpServletResponseWrapper((HttpServletResponse)servletResponse);
-            gzipResponse.addHeader(HttpHeader.Content_Encoding.getHttpName(), "gzip");
-            filterChain.doFilter(servletRequest, gzipResponse);
-            gzipResponse.finish();
-
+        if ( enabled && interestInRequest( servletRequest )) {
+            compressingFilter.doFilter( servletRequest, servletResponse, filterChain );
         } else {
             filterChain.doFilter(servletRequest, servletResponse);
         }
     }
 
-    private boolean isEnabled(final ServletRequest servletRequest) {
-
+    private boolean interestInRequest( final ServletRequest servletRequest) {
         try {
             final PwmURL pwmURL = new PwmURL((HttpServletRequest) servletRequest);
-            if (pwmURL.isResourceURL() || pwmURL.isRestService()) {
+
+            // resource servlet does its own gzip compression with fancy server-side caching
+            if (pwmURL.isResourceURL()) {
                 return false;
             }
         } catch (Exception e) {
             LOGGER.error("unable to parse request url, defaulting to non-gzip: " + e.getMessage());
         }
 
-        final PwmApplication pwmApplication;
-        try {
-            pwmApplication = ContextManager.getPwmApplication((HttpServletRequest) servletRequest);
-            return Boolean.parseBoolean(pwmApplication.getConfig().readAppProperty(AppProperty.HTTP_ENABLE_GZIP));
-        } catch (PwmUnrecoverableException e) {
-            //LOGGER.trace("unable to read http-gzip app-property, defaulting to non-gzip: " + e.getMessage());
-        }
-        return false;
-    }
-
-
-    public static class GZIPHttpServletResponseWrapper extends HttpServletResponseWrapper {
-        private ServletResponseGZIPOutputStream gzipStream;
-        private ServletOutputStream outputStream;
-        private PrintWriter printWriter;
-
-        public GZIPHttpServletResponseWrapper(final HttpServletResponse response) throws IOException {
-            super(response);
-        }
-
-        public void finish() throws IOException {
-            if (printWriter != null) {
-                printWriter.close();
-            }
-            if (outputStream != null) {
-                outputStream.close();
-            }
-            if (gzipStream != null) {
-                gzipStream.close();
-            }
-        }
-
-        @Override
-        public void flushBuffer() throws IOException {
-            if (printWriter != null) {
-                printWriter.flush();
-            }
-            if (outputStream != null) {
-                outputStream.flush();
-            }
-            super.flushBuffer();
-        }
-
-        @Override
-        public ServletOutputStream getOutputStream() throws IOException {
-            if (printWriter != null) {
-                throw new IllegalStateException("getWriter() has previously been invoked, can not call getOutputStream()");
-            }
-            if (outputStream == null) {
-                initGzip();
-                outputStream = gzipStream;
-            }
-            return outputStream;
-        }
-
-        @Override
-        public PrintWriter getWriter() throws IOException {
-            if (outputStream != null) {
-                throw new IllegalStateException("getOutputStream() has previously been invoked, can not call getWriter()");
-            }
-            if (printWriter == null) {
-                initGzip();
-                printWriter = new PrintWriter(new OutputStreamWriter(gzipStream, getResponse().getCharacterEncoding()));
-            }
-            return printWriter;
-        }
-
-        @Override
-        public void setContentLength(final int len) {
-        }
-
-        private void initGzip() throws IOException {
-            gzipStream = new ServletResponseGZIPOutputStream(getResponse().getOutputStream());
-        }
-    }
-
-    public static class ServletResponseGZIPOutputStream extends ServletOutputStream {
-        private final AtomicBoolean open = new AtomicBoolean(true);
-        private GZIPOutputStream gzipStream;
-
-        public ServletResponseGZIPOutputStream(final ServletOutputStream output) throws IOException {
-            gzipStream = new GZIPOutputStream(output);
-        }
-
-        @Override
-        public void close() throws IOException {
-            if (open.compareAndSet(true, false)) {
-                gzipStream.close();
-            }
-        }
-
-        @Override
-        public void flush() throws IOException {
-            gzipStream.flush();
-        }
-
-        @Override
-        public void write(final byte[] b) throws IOException {
-            write(b, 0, b.length);
-        }
-
-        @Override
-        public void write(final byte[] b, final int off, final int len) throws IOException {
-            if (!open.get()) {
-                throw new IOException("Stream closed!");
-            }
-            gzipStream.write(b, off, len);
-        }
-
-        @Override
-        public void write(final int b) throws IOException {
-            if (!open.get()) {
-                throw new IOException("Stream closed!");
-            }
-            gzipStream.write(b);
-        }
-
-        /*
-        // servlet 3.1 method
-        public void setWriteListener(WriteListener writeListener)
-        {
-            servletOutputStream.setWriteListener(writeListener);
-        }
-
-        // servlet 3.1 method
-        public boolean isReady()
-        {
-            return servletOutputStream.isReady();
-        }
-        */
+        return true;
     }
 }

+ 62 - 49
server/src/main/java/password/pwm/http/filter/RequestInitializationFilter.java

@@ -155,7 +155,7 @@ public class RequestInitializationFilter implements Filter {
         } catch (Throwable e) {
             LOGGER.error("can't load application: " + e.getMessage(),e);
             if (!(new PwmURL(req).isResourceURL())) {
-                respondWithUnavalailbleError(req, resp);
+                respondWithUnavailableError(req, resp);
                 return;
             }
             return;
@@ -191,7 +191,7 @@ public class RequestInitializationFilter implements Filter {
                 LOGGER.error(logMsg,e);
             }
             if (!(new PwmURL(req).isResourceURL())) {
-                respondWithUnavalailbleError(req, resp);
+                respondWithUnavailableError(req, resp);
                 return;
             }
             return;
@@ -200,7 +200,7 @@ public class RequestInitializationFilter implements Filter {
         filterChain.doFilter(req, resp);
     }
 
-    private void respondWithUnavalailbleError(final HttpServletRequest req, final HttpServletResponse resp)
+    private void respondWithUnavailableError( final HttpServletRequest req, final HttpServletResponse resp)
             throws ServletException, IOException
     {
         ErrorInformation errorInformation = new ErrorInformation(PwmError.ERROR_APP_UNAVAILABLE);
@@ -307,84 +307,86 @@ public class RequestInitializationFilter implements Filter {
             return;
         }
 
+        final boolean includeXSessionID = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XSESSIONID));
+        if (includeXSessionID && pwmSession != null) {
+            resp.setHeader(HttpHeader.XSessionID, pwmSession.getSessionStateBean().getSessionID());
+        }
+
+        final boolean includeContentLanguage = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_CONTENT_LANGUAGE));
+        if (includeContentLanguage) {
+            resp.setHeader(HttpHeader.Content_Language, pwmRequest.getLocale().toLanguageTag());
+        }
+
+        addStaticResponseHeaders( pwmApplication, resp.getHttpServletResponse() );
+
+
+        if (pwmSession != null) {
+            final String contentPolicy;
+            if (pwmRequest.getURL().isConfigGuideURL() || pwmRequest.getURL().isConfigManagerURL()) {
+                contentPolicy = config.readAppProperty(AppProperty.SECURITY_HTTP_CONFIG_CSP_HEADER);
+            } else {
+                contentPolicy = config.readSettingAsString(PwmSetting.SECURITY_CSP_HEADER);
+            }
+
+            if (contentPolicy != null && !contentPolicy.isEmpty()) {
+                final String nonce = pwmRequest.getCspNonce();
+                final String expandedPolicy = contentPolicy.replace("%NONCE%", nonce);
+                resp.setHeader(HttpHeader.ContentSecurityPolicy, expandedPolicy);
+            }
+        }
+    }
+
+    public static void addStaticResponseHeaders(final PwmApplication pwmApplication, final HttpServletResponse resp) throws PwmUnrecoverableException
+    {
+        final Configuration config = pwmApplication.getConfig();
+
         final String serverHeader = config.readAppProperty(AppProperty.HTTP_HEADER_SERVER);
         final boolean includeXInstance = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XINSTANCE));
-        final boolean includeXSessionID = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XSESSIONID));
         final boolean includeXVersion = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XVERSION));
         final boolean includeXContentTypeOptions = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XCONTENTTYPEOPTIONS));
         final boolean includeXXSSProtection = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XXSSPROTECTION));
+        final boolean includeXFrameDeny = config.readSettingAsBoolean(PwmSetting.SECURITY_PREVENT_FRAMING);
+        final boolean includeXAmb = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XAMB));
 
-        final boolean sendNoise = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XNOISE));
-
-        if (sendNoise) {
-            final int noiseLength = Integer.parseInt(config.readAppProperty(AppProperty.HTTP_HEADER_NOISE_LENGTH));
-            resp.setHeader(
-                    HttpHeader.XNoise,
-                    PwmRandom.getInstance().alphaNumericString(PwmRandom.getInstance().nextInt(noiseLength)+11)
-            );
+        {
+            final String noiseHeader = makeNoiseHeader( config );
+            if (noiseHeader != null) {
+                resp.setHeader( HttpHeader.XNoise.getHttpName(), noiseHeader );
+            }
         }
 
         if (includeXVersion) {
-            resp.setHeader(HttpHeader.XVersion, PwmConstants.SERVLET_VERSION);
+            resp.setHeader(HttpHeader.XVersion.getHttpName(), PwmConstants.SERVLET_VERSION);
         }
 
         if (includeXContentTypeOptions) {
-            resp.setHeader(HttpHeader.XContentTypeOptions, "nosniff");
+            resp.setHeader(HttpHeader.XContentTypeOptions.getHttpName(), "nosniff");
         }
 
         if (includeXXSSProtection) {
-            resp.setHeader(HttpHeader.XXSSProtection, "1");
+            resp.setHeader(HttpHeader.XXSSProtection.getHttpName(), "1");
         }
 
         if (includeXInstance) {
-            resp.setHeader(HttpHeader.XInstance, String.valueOf(pwmApplication.getInstanceID()));
-        }
-
-        if (includeXSessionID && pwmSession != null) {
-            resp.setHeader(HttpHeader.XSessionID, pwmSession.getSessionStateBean().getSessionID());
+            resp.setHeader(HttpHeader.XInstance.getHttpName(), String.valueOf(pwmApplication.getInstanceID()));
         }
 
         if (serverHeader != null && !serverHeader.isEmpty()) {
             final String value = MacroMachine.forNonUserSpecific(pwmApplication, null).expandMacros(serverHeader);
-            resp.setHeader(HttpHeader.Server, value);
+            resp.setHeader(HttpHeader.Server.getHttpName(), value);
         }
 
-        // ----- non-resource urls only for the following operations -----
-
-        final boolean includeXFrameDeny = config.readSettingAsBoolean(PwmSetting.SECURITY_PREVENT_FRAMING);
-        final boolean includeXAmb = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_XAMB));
-        final boolean includeContentLanguage = Boolean.parseBoolean(config.readAppProperty(AppProperty.HTTP_HEADER_SEND_CONTENT_LANGUAGE));
-
         if (includeXFrameDeny) {
-            resp.setHeader(HttpHeader.XFrameOptions, "DENY");
+            resp.setHeader(HttpHeader.XFrameOptions.getHttpName(), "DENY");
         }
 
         if (includeXAmb) {
-            resp.setHeader(HttpHeader.XAmb, PwmConstants.X_AMB_HEADER.get(
+            resp.setHeader(HttpHeader.XAmb.getHttpName(), PwmConstants.X_AMB_HEADER.get(
                     PwmRandom.getInstance().nextInt(PwmConstants.X_AMB_HEADER.size())
             ));
         }
 
-        if (includeContentLanguage) {
-            resp.setHeader(HttpHeader.Content_Language, pwmRequest.getLocale().toLanguageTag());
-        }
-
-        resp.setHeader(HttpHeader.Cache_Control, "no-cache, no-store, must-revalidate, proxy-revalidate");
-
-        if (pwmSession != null) {
-            final String contentPolicy;
-            if (pwmRequest.getURL().isConfigGuideURL() || pwmRequest.getURL().isConfigManagerURL()) {
-                contentPolicy = config.readAppProperty(AppProperty.SECURITY_HTTP_CONFIG_CSP_HEADER);
-            } else {
-                contentPolicy = config.readSettingAsString(PwmSetting.SECURITY_CSP_HEADER);
-            }
-
-            if (contentPolicy != null && !contentPolicy.isEmpty()) {
-                final String nonce = pwmRequest.getCspNonce();
-                final String expandedPolicy = contentPolicy.replace("%NONCE%", nonce);
-                resp.setHeader(HttpHeader.ContentSecurityPolicy, expandedPolicy);
-            }
-        }
+        resp.setHeader(HttpHeader.Cache_Control.getHttpName(), "no-cache, no-store, must-revalidate, proxy-revalidate");
     }
 
 
@@ -685,4 +687,15 @@ public class RequestInitializationFilter implements Filter {
         return StringUtil.mapToString(values);
     }
 
+    private static String makeNoiseHeader(final Configuration configuration) {
+        final boolean sendNoise = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.HTTP_HEADER_SEND_XNOISE));
+
+        if (sendNoise) {
+            final int noiseLength = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_HEADER_NOISE_LENGTH));
+            return PwmRandom.getInstance().alphaNumericString(PwmRandom.getInstance().nextInt(noiseLength)+11);
+        }
+
+        return null;
+    }
+
 }

+ 53 - 0
server/src/main/java/password/pwm/ldap/PwmLdapVendor.java

@@ -0,0 +1,53 @@
+package password.pwm.ldap;
+
+import com.novell.ldapchai.provider.DirectoryVendor;
+
+public enum PwmLdapVendor {
+    ACTIVE_DIRECTORY(DirectoryVendor.ACTIVE_DIRECTORY, "MICROSOFT_ACTIVE_DIRECTORY"),
+    EDIRECTORY(DirectoryVendor.EDIRECTORY, "NOVELL_EDIRECTORY"),
+    OPEN_LDAP(DirectoryVendor.OPEN_LDAP),
+    DIRECTORY_SERVER_389(DirectoryVendor.DIRECTORY_SERVER_389),
+    ORACLE_DS(DirectoryVendor.ORACLE_DS),
+    GENERIC(DirectoryVendor.GENERIC),
+
+    ;
+
+    private final DirectoryVendor chaiVendor;
+    private final String[] otherNames;
+
+    PwmLdapVendor( final DirectoryVendor directoryVendor, final String... otherNames ) {
+        this.chaiVendor = directoryVendor;
+        this.otherNames = otherNames;
+    }
+
+    public static PwmLdapVendor fromString(final String input) {
+        if (input == null) {
+            return null;
+        }
+
+        for (PwmLdapVendor vendor : PwmLdapVendor.values()) {
+            if ( vendor.name().equals( input ) ) {
+                return vendor;
+            }
+
+            if (vendor.otherNames != null) {
+                for (final String otherName : vendor.otherNames) {
+                    if (otherName.equals( input )) {
+                        return vendor;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public static PwmLdapVendor fromChaiVendor(final DirectoryVendor directoryVendor) {
+        for (PwmLdapVendor vendor : PwmLdapVendor.values()) {
+            if (vendor.chaiVendor == directoryVendor) {
+                return vendor;
+            }
+        }
+        return null;
+    }
+}

+ 4 - 2
server/src/main/java/password/pwm/svc/sessiontrack/SessionTrackService.java

@@ -28,12 +28,12 @@ import password.pwm.PwmApplication;
 import password.pwm.bean.LocalSessionStateBean;
 import password.pwm.bean.LoginInfoBean;
 import password.pwm.bean.UserIdentity;
-import password.pwm.error.PwmUnrecoverableException;
-import password.pwm.ldap.UserInfo;
 import password.pwm.bean.pub.SessionStateInfoBean;
 import password.pwm.error.PwmException;
+import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
 import password.pwm.http.PwmSession;
+import password.pwm.ldap.UserInfo;
 import password.pwm.svc.PwmService;
 import password.pwm.util.logging.PwmLogger;
 
@@ -214,4 +214,6 @@ public class SessionTrackService implements PwmService {
     public List<UserIdentity> getRecentLogins() {
         return Collections.unmodifiableList(new ArrayList<>(recentLoginCache.asMap().keySet()));
     }
+
+
 }

+ 65 - 0
server/src/main/java/password/pwm/svc/sessiontrack/UserAgentUtils.java

@@ -0,0 +1,65 @@
+package password.pwm.svc.sessiontrack;
+
+import com.blueconic.browscap.Capabilities;
+import com.blueconic.browscap.ParseException;
+import com.blueconic.browscap.UserAgentParser;
+import com.blueconic.browscap.UserAgentService;
+import password.pwm.error.ErrorInformation;
+import password.pwm.error.PwmError;
+import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.http.HttpHeader;
+import password.pwm.http.PwmRequest;
+import password.pwm.util.java.StringUtil;
+import password.pwm.util.logging.PwmLogger;
+
+import java.io.IOException;
+
+public class UserAgentUtils  {
+    private static final PwmLogger LOGGER = PwmLogger.forClass( UserAgentUtils.class );
+
+    private static UserAgentParser cachedParser;
+
+    private static UserAgentParser getUserAgentParser() throws PwmUnrecoverableException
+    {
+        if (cachedParser == null) {
+            try {
+                cachedParser = new UserAgentService().loadParser();
+            }  catch ( IOException | ParseException e ) {
+                final String msg = "error loading user-agent parser: " + e.getMessage();
+                LOGGER.error( msg, e );
+                throw new PwmUnrecoverableException( PwmError.ERROR_UNKNOWN, msg );
+            }
+        }
+        return cachedParser;
+    }
+
+    public static void checkIfPreIE11( final PwmRequest pwmRequest) throws PwmUnrecoverableException  {
+        final String userAgentString = pwmRequest.readHeaderValueAsString( HttpHeader.UserAgent);
+        if ( StringUtil.isEmpty( userAgentString )) {
+            return ;
+        }
+
+        boolean badBrowser = false;
+
+        final UserAgentParser userAgentParser = getUserAgentParser();
+        final Capabilities capabilities = userAgentParser.parse( userAgentString );
+        final String browser = capabilities.getBrowser();
+        final String browserMajorVersion = capabilities.getBrowserMajorVersion();
+
+        if ("IE".equalsIgnoreCase( browser )) {
+            try {
+                final int majorVersionInt = Integer.parseInt( browserMajorVersion );
+                if (majorVersionInt <= 10) {
+                    badBrowser = true;
+                }
+            }  catch ( NumberFormatException e ) {
+                LOGGER.error( "error parsing user-agent major version" + e.getMessage(), e );
+            }
+        }
+
+        if (badBrowser) {
+            final String errorMsg = "Internet Explorer version is not supported for this function.  Please use Internet Explorer 11 or higher or another web browser.";
+            throw new PwmUnrecoverableException(new ErrorInformation( PwmError.ERROR_UNAUTHORIZED, errorMsg));
+        }
+    }
+}

+ 5 - 2
server/src/main/java/password/pwm/svc/telemetry/TelemetryService.java

@@ -40,6 +40,7 @@ import password.pwm.error.PwmError;
 import password.pwm.error.PwmException;
 import password.pwm.error.PwmUnrecoverableException;
 import password.pwm.health.HealthRecord;
+import password.pwm.ldap.PwmLdapVendor;
 import password.pwm.svc.PwmService;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsBundle;
@@ -258,10 +259,12 @@ public class TelemetryService implements PwmService {
             }
         }
 
-        final Set<DirectoryVendor> ldapVendors = new LinkedHashSet<>();
+        final Set<PwmLdapVendor> ldapVendors = new LinkedHashSet<>();
         for (final LdapProfile ldapProfile : config.getLdapProfiles().values()) {
             try {
-                ldapVendors.add(ldapProfile.getProxyChaiProvider(pwmApplication).getDirectoryVendor());
+                final DirectoryVendor directoryVendor = ldapProfile.getProxyChaiProvider(pwmApplication).getDirectoryVendor();
+                final PwmLdapVendor pwmLdapVendor = PwmLdapVendor.fromChaiVendor(directoryVendor);
+                ldapVendors.add(pwmLdapVendor);
             } catch (Exception e) {
                 LOGGER.trace(SessionLabel.TELEMETRY_SESSION_LABEL, "unable to read ldap vendor type for stats publication: " + e.getMessage());
             }

+ 11 - 0
server/src/main/java/password/pwm/util/java/JsonUtil.java

@@ -33,6 +33,7 @@ import com.google.gson.JsonSerializationContext;
 import com.google.gson.JsonSerializer;
 import com.google.gson.reflect.TypeToken;
 import password.pwm.error.PwmUnrecoverableException;
+import password.pwm.ldap.PwmLdapVendor;
 import password.pwm.util.PasswordData;
 import password.pwm.util.logging.PwmLogger;
 
@@ -259,6 +260,16 @@ public class JsonUtil {
 
     }
 
+    private static class PwmLdapVendorTypeAdaptor implements JsonSerializer<PwmLdapVendor>, JsonDeserializer<PwmLdapVendor> {
+        public PwmLdapVendor deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
+                return PwmLdapVendor.fromString( json.getAsString() );
+        }
+
+        public JsonElement serialize(final PwmLdapVendor src, final Type typeOfSrc, final JsonSerializationContext context) {
+                return new JsonPrimitive(src.name());
+        }
+    }
+
     private static GsonBuilder registerTypeAdapters(final GsonBuilder gsonBuilder) {
         gsonBuilder.registerTypeAdapter(Date.class, new DateTypeAdapter());
         gsonBuilder.registerTypeAdapter(Instant.class, new InstantTypeAdapter());

+ 2 - 1
server/src/main/java/password/pwm/ws/server/RestServlet.java

@@ -104,6 +104,8 @@ public abstract class RestServlet extends HttpServlet{
 
             final RestRequest restRequest = RestRequest.forRequest(pwmApplication, restAuthentication, sessionLabel, req);
 
+            RequestInitializationFilter.addStaticResponseHeaders( pwmApplication, resp );
+
             preCheck(restRequest);
 
             preCheckRequest(restRequest);
@@ -372,5 +374,4 @@ public abstract class RestServlet extends HttpServlet{
             throw new PwmUnrecoverableException(e.getErrorInformation());
         }
     }
-
 }

+ 1 - 0
server/src/main/resources/password/pwm/AppProperty.properties

@@ -61,6 +61,7 @@ config.fileScanFrequencyMS=5017
 config.newuser.passwordPolicyCacheMS=3600000
 config.theme=pwm
 config.enableJbCryptPwLibrary=true
+configEditor.blockOldIE=true
 configEditor.queryFilter.testLimit=5000
 configEditor.idleTimeoutSeconds=900
 configGuide.idleTimeoutSeconds=3600

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