You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by jf...@apache.org on 2016/09/30 16:56:46 UTC

svn commit: r1762935 - in /vcl/trunk/web: .ht-inc/addomain.php .ht-inc/image.php .ht-inc/resource.php .ht-inc/states.php .ht-inc/utils.php css/vcl.css js/resources/addomain.js js/resources/image.js themes/dropdownmenus/css/theme.css

Author: jfthomps
Date: Fri Sep 30 16:56:45 2016
New Revision: 1762935

URL: http://svn.apache.org/viewvc?rev=1762935&view=rev
Log:
VCL-277 - Add support for images to join Active Directory domains
VCL-867 - Active Directory Authentication for Windows VM's

incorporated patches from Junaid Ali attached to VCL-867; the VCL resource code has been largely rewritten since the patches were generated, so that part needed to be rewritten as well; shortened some of the names of fields and tables

addomain.php: initial add; definition of ADdomain class which inherits from Resource class

image.php:
-modified fieldWidth: added os, addomain, baseOU, adauthenabled
-modified fieldDisplayName: added adauthenabled, addomain, baseOU
-modified addEditDialogHTML: added fieldset and fields for AD authentication; added tooltip for baseOU
-modified AJsaveResource: added section that inserts/deletes/updates imageaddomain table as appropriate
-modified addResource: added section to insert into imageaddomain table as appropriate
-modified validateResourceData: added array keys and validation for adauthendabled, addomainid, baseou

resource.php:
-modified resource: added case statement for addomain
-modified viewResources and fieldWidth: added conditional for Edge browser for setting widths (unrelated to these JIRAs)
-added checkExistingField 

states.php:
-added addomain to $actions["entry"]
-added addomain to $actions['mode'], $actions['args'], and $actions['pages']

utils.php:
-modified initGlobals: added case for addomain in section that require_once's resource.php
-modified getImages: added addomainid, addomain, and baseOU to query and returned data; added adauthenabled to returned data
-modified getResourceGroupMembers: added conditional for $type == addomain; added addomain to $names, $joins, and $orders
-added getADdomains
-modified labeledFormItem: added case for password to go along with text case; changed hard coded 'text' for input type to be $type so it matches the passed in type to be either text or password
-added validateHostname
-modified getNavMenuData: added AD Domains menu entry for users with addomainAdmin
-modified getDojoHTML: added addomain case statement to include addomain.js to switch statement in groupMapHTML, viewResources, and editConfigMap case statements

image.js:
-modified Image.prototype.colformatter: added adauthenabled for true/false part; added addomain and baseOU to (unset) part
-modified inlineEditResourceCB: added showing/hiding of imageadauthbox; added setting of adauthenable, addomainid, and baseou
-modified resetEditResource: added resets for adauthenable, addomainid, and baseou
-modified saveResource: added for validation of baseou; added adauthenabled, addomainid, and baseou to submitted data
-modified saveResourceCB: added setting for adauthenabled, addomainid, addomain, and baseOU fields in store object
-modified startImageCB: added resetting of adauthenable, addomainid, and baseou and showing/hiding of imageauthbox based on OS type
-added toggleADauth

vcl.css:
-added:
#addomaindlgcontent label
#addomaindlgcontent .labeledform

dropdownmenus/css/theme.css:
-added:
#renameDialog tbody tr:hover td
#renameDialog tbody tr td
div.dijitDialog h2
div.dijitDialog h3
#confdelcontent h3
#groupbyresourcediv table.dojoxGridRowTable tbody tr:hover td
#groupbyresourcediv div.dojoxGridRowSelected table.dojoxGridRowTable tbody tr td
#imageadauthbox label
#imageadauthbox .labeledform

Added:
    vcl/trunk/web/.ht-inc/addomain.php
    vcl/trunk/web/js/resources/addomain.js
Modified:
    vcl/trunk/web/.ht-inc/image.php
    vcl/trunk/web/.ht-inc/resource.php
    vcl/trunk/web/.ht-inc/states.php
    vcl/trunk/web/.ht-inc/utils.php
    vcl/trunk/web/css/vcl.css
    vcl/trunk/web/js/resources/image.js
    vcl/trunk/web/themes/dropdownmenus/css/theme.css

Added: vcl/trunk/web/.ht-inc/addomain.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/addomain.php?rev=1762935&view=auto
==============================================================================
--- vcl/trunk/web/.ht-inc/addomain.php (added)
+++ vcl/trunk/web/.ht-inc/addomain.php Fri Sep 30 16:56:45 2016
@@ -0,0 +1,653 @@
+<?php
+/*
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+///
+/// \class ADdomain
+///
+/// \brief extends Resource class to add things specific to resources of the
+/// addomain type
+///
+////////////////////////////////////////////////////////////////////////////////
+class ADdomain extends Resource {
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn __construct()
+	///
+	/// \brief calls parent constructor; initializes things for Schedule class
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function __construct() {
+		parent::__construct();
+		$this->restype = 'addomain';
+		$this->restypename = 'AD Domain';
+		$this->namefield = 'name';
+		$this->basecdata['obj'] = $this;
+		$this->deletable = 1;
+		$this->deletetoggled = 0;
+		$this->defaultGetDataArgs = array('rscid' => 0);
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn getData($args)
+	///
+	/// \param $args - array of arguments that determine what data gets returned;
+	/// must include:\n
+	/// \b rscid - only return data for resource with this id; pass 0 for all
+	/// (from addomain table)
+	///
+	/// \return array of data as returned from getADdomains
+	///
+	/// \brief wrapper for calling getADdomains
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function getData($args) {
+		return getADdomains($args['rscid']);
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn fieldWidth($field)
+	///
+	/// \param $field - name of a resource field
+	///
+	/// \return string for setting width of field (includes width= part)
+	///
+	/// \brief generates the required width for the field; can return an empty
+	/// string if field should default to auto width
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function fieldWidth($field) {
+		switch($field) {
+			case 'name':
+				$w = 17;
+				break;
+			case 'owner':
+				$w = 11;
+				break;
+			case 'domaindnsname':
+				$w = 12;
+				break;
+			case 'domainnetbiosname':
+				$w = 12;
+				break;
+			case 'username':
+				$w = 9;
+				break;
+			case 'dnsservers':
+				$w = 12;
+				break;
+			case 'domaincontrollers':
+				$w = 17;
+				break;
+			case 'logindescription':
+				$w = 12;
+				break;
+			default:
+				return '';
+		}
+		if(preg_match('/MSIE/i', $_SERVER['HTTP_USER_AGENT']) ||
+		   preg_match('/Trident/i', $_SERVER['HTTP_USER_AGENT']) ||
+		   preg_match('/Edge/i', $_SERVER['HTTP_USER_AGENT']))
+			$w = round($w * 11.5) . 'px';
+		else
+			$w = "{$w}em";
+		return "width=\"$w\"";
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn fieldDisplayName($field)
+	///
+	/// \param $field - name of a resource field
+	///
+	/// \return display value for $field
+	///
+	/// \brief generates the display value for $field
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function fieldDisplayName($field) {
+		switch($field) {
+			case 'domaindnsname':
+				return 'Domain DNS Name';
+			case 'domainnetbiosname':
+				return 'Domain NetBIOS Name';
+			case 'dnsservers':
+				return 'DNS Server(s)';
+			case 'domaincontrollers':
+				return 'Domain Controller(s)';
+			case 'logindescription':
+				return 'Login Description';
+		}
+		return ucfirst($field);
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn AJsaveResource()
+	///
+	/// \brief saves changes to a resource; must be implemented by inheriting
+	/// class
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function AJsaveResource() {
+		global $user;
+		$add = getContinuationVar('add', 0);
+		$data = $this->validateResourceData();
+		if($data['error']) {
+			$ret = array('status' => 'error', 'msg' => $data['errormsg']);
+			sendJSON($ret);
+			return;
+		}
+
+		if($add) {
+			$esc_name = mysql_real_escape_string($data['name']);
+			$query = "SELECT id FROM addomain WHERE name = '$esc_name'";
+			$qh = doQuery($query);
+			if($row = mysql_fetch_assoc($qh)) {
+				sendJSON(array('status' => 'adderror',
+				               'errormsg' => wordwrap(i('An AD Domain with this name already exists.'), 75, '<br>')));
+				return;
+			}
+			if(! $data['rscid'] = $this->addResource($data)) {
+				sendJSON(array('status' => 'adderror',
+				               'errormsg' => wordwrap(i('Error encountered while trying to create new AD domain. Please contact an admin for assistance.'), 75, '<br>')));
+				return;
+			}
+		}
+		else {
+			$olddata = getContinuationVar('olddata');
+			$updates = array();
+			# name
+			if($data['name'] != $olddata['name'])
+				$updates[] = "name = '{$data['name']}'";
+			# ownerid
+			$ownerid = getUserlistID($data['owner']);
+			if($ownerid != $olddata['ownerid'])
+				$updates[] = "ownerid = $ownerid";
+			# domaindnsname
+			if($data['domaindnsname'] != $olddata['domaindnsname'])
+				$updates[] = "domainDNSName = '{$data['domaindnsname']}'";
+			# domainnetbiosname
+			if($data['domainnetbiosname'] != $olddata['domainnetbiosname'])
+				$updates[] = "domainNetBIOSName = '{$data['domainnetbiosname']}'";
+			# username
+			if($data['username'] != $olddata['username'])
+				$updates[] = "username = '{$data['username']}'";
+			# password
+			if(strlen($data['password'])) {
+				$esc_pass = mysql_real_escape_string($data['password']);
+				$updates[] = "password = '$esc_pass'";
+			}
+			# dnsservers
+			if($data['dnsservers'] != $olddata['dnsservers'])
+				$updates[] = "dnsServers = '{$data['dnsservers']}'";
+			# domaincontrollers
+			if($data['domaincontrollers'] != $olddata['domaincontrollers'])
+				$updates[] = "domainControllers = '{$data['domaincontrollers']}'";
+			# logindescription
+			if($data['logindescription'] != $olddata['logindescription']) {
+				$esc_desc = mysql_real_escape_string($data['logindescription']);
+				$updates[] = "logindescription = '$esc_desc'";
+			}
+			if(count($updates)) {
+				$query = "UPDATE addomain SET "
+				       . implode(', ', $updates)
+				       . " WHERE id = {$data['rscid']}";
+				doQuery($query);
+			}
+		}
+
+		# clear user resource cache for this type
+		$key = getKey(array(array($this->restype . "Admin"), array("administer"), 0, 1, 0, 0));
+		unset($_SESSION['userresources'][$key]);
+		$key = getKey(array(array($this->restype . "Admin"), array("administer"), 0, 0, 0, 0));
+		unset($_SESSION['userresources'][$key]);
+		$key = getKey(array(array($this->restype . "Admin"), array("manageGroup"), 0, 1, 0, 0));
+		unset($_SESSION['userresources'][$key]);
+		$key = getKey(array(array($this->restype . "Admin"), array("manageGroup"), 0, 0, 0, 0));
+		unset($_SESSION['userresources'][$key]);
+
+		$tmp = $this->getData(array('rscid' => $data['rscid']));
+		$data = $tmp[$data['rscid']];
+		$arr = array('status' => 'success');
+		$arr['data'] = $data;
+		if($add) {
+			$arr['action'] = 'add';
+			$arr['nogroups'] = 0;
+			$groups = getUserResources(array($this->restype . 'Admin'), array('manageGroup'), 1);
+			if(count($groups[$this->restype]))
+				$arr['groupingHTML'] = $this->groupByResourceHTML();
+			else
+				$arr['nogroups'] = 1;
+		}
+		else
+			$arr['action'] = 'edit';
+		sendJSON($arr);
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn AJeditResource()
+	///
+	/// \brief sends data for editing a resource
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function AJeditResource() {
+		$rscid = processInputVar('rscid', ARG_NUMERIC);
+		$resources = getUserResources(array($this->restype . 'Admin'), array('administer'), 0, 1);
+		if(! array_key_exists($rscid, $resources[$this->restype])) {
+			$ret = array('status' => 'noaccess');
+			sendJSON($ret);
+			return;
+		}
+		$args = $this->defaultGetDataArgs;
+		$args['rscid'] = $rscid;
+		$tmp = $this->getData($args);
+		$data = $tmp[$rscid];
+		$login = preg_replace("/<br>/", "\n", $data['logindescription']);
+		$data['logindescription'] = htmlspecialchars_decode($login);
+		$cdata = $this->basecdata;
+		$cdata['rscid'] = $rscid;
+		$cdata['olddata'] = $data;
+
+		# save continuation
+		$cont = addContinuationsEntry('AJsaveResource', $cdata);
+
+		$ret = $this->jsondata;
+		$ret['title'] = "Edit {$this->restypename}";
+		$ret['cont'] = $cont;
+		$ret['resid'] = $rscid;
+		$ret['data'] = $data;
+		$ret['status'] = 'success';
+		sendJSON($ret);
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn addResource($data)
+	///
+	/// \param $data - array of needed data for adding a new resource
+	///
+	/// \return id of new resource
+	///
+	/// \brief handles all parts of adding a new resource to the database; should
+	/// be implemented by inheriting class, but not required since it is only
+	/// called by functions in the inheriting class (nothing in this base class
+	/// calls it directly)
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function addResource($data) {
+		global $user;
+
+		$ownerid = getUserlistID($data['owner']);
+		$esc_pass = mysql_real_escape_string($data['password']);
+		$esc_desc = mysql_real_escape_string($data['logindescription']);
+	
+		$query = "INSERT INTO addomain"
+				.	"(name,"
+				.	"ownerid,"
+				.	"domainDNSName,"
+				.	"domainNetBIOSName,"
+				.	"username,"
+				.	"password,"
+				.	"dnsServers,"
+				.	"domainControllers,"
+				.	"logindescription)"
+				.	"VALUES ('{$data['name']}',"
+				.	"$ownerid,"
+				.	"'{$data['domaindnsname']}',"
+				.	"'{$data['domainnetbiosname']}',"
+				.	"'{$data['username']}',"
+				.	"'$esc_pass',"
+				.	"'{$data['dnsservers']}',"
+				.	"'{$data['domaincontrollers']}',"
+				.	"'$esc_desc')";
+		doQuery($query);
+
+		$rscid = dbLastInsertID();
+		if($rscid == 0) {
+			return 0;
+		}
+		// add entry in resource table
+		$query = "INSERT INTO resource "
+				 .        "(resourcetypeid, "
+				 .        "subid) "
+				 . "VALUES (19, "
+				 .        "$rscid)";
+		doQuery($query);
+		return $rscid;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn addEditDialogHTML($add)
+	///
+	/// \param $add - unused for this class
+	///
+	/// \brief handles generating HTML for dialog used to edit resource
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function addEditDialogHTML($add=0) {
+		global $user, $days;
+		# dialog for on page editing
+		$h = '';
+		$h .= "<div dojoType=dijit.Dialog\n";
+		$h .= "      id=\"addeditdlg\"\n";
+		$h .= "      title=\"Edit {$this->restypename}\"\n";
+		$h .= "      duration=250\n";
+		#$h .= "      style=\"width: 70%;\"\n";
+		$h .= "      draggable=true>\n";
+		$h .= "<div id=\"addeditdlgcontent\">\n";
+		$h .= "<div id=\"addomaindlgcontent\">\n";
+		# id
+		$h .= "<input type=\"hidden\" id=\"editresid\">\n";
+
+		# todo consider adding help icons with popups
+
+		$h .= "<div style=\"text-align: center;\">\n";
+		# name
+		$errmsg = i("Name cannot contain single (') or double (&quot;) quotes, less than (&lt;), or greater than (&gt;) and can be from 2 to 30 characters long");
+		$h .= labeledFormItem('name', i('Name'), 'text', '^([A-Za-z0-9-!@#$%^&\*\(\)_=\+\[\]{}\\\|:;,\./\?~` ]){2,30}$',
+		                      1, '', $errmsg, '', '', '200px'); 
+		# owner
+		$extra = array('onKeyPress' => 'setOwnerChecking');
+		$h .= labeledFormItem('owner', i('Owner'), 'text', '', 1,
+		                      "{$user['unityid']}@{$user['affiliation']}", i('Unknown user'),
+		                      'checkOwner', $extra, '200px');
+		$cont = addContinuationsEntry('AJvalidateUserid');
+		$h .= "<input type=\"hidden\" id=\"valuseridcont\" value=\"$cont\">\n";
+		# domain dns name
+		$hostbase = '([A-Za-z0-9]{1,63})(\.[A-Za-z0-9-_]+)*(\.?[A-Za-z0-9])';
+		$errmsg = i("Domain DNS Name should be in the format domain.tld and can only contain letters, numbers, dashes(-), periods(.), and underscores(_) (e.g. myuniversity.edu)");
+		$h .= labeledFormItem('domaindnsname', i('Domain DNS Name'), 'text', "^$hostbase$",
+		                      1, '', $errmsg, '', '', '200px'); 
+		# domain netbios name
+		$errmsg = i("Domain NetBIOS Name can only contain letters, numbers, dashes(-), periods(.), and underscores(_) and can be up to 15 characters long");
+		$h .= labeledFormItem('domainnetbiosname', i('Domain NetBIOS Name'), 'text', '^[a-zA-Z0-9_][-a-zA-Z0-9_\.]{0,14}$',
+		                      1, '', $errmsg, '', '', '200px'); 
+		$h .= "<br>\n";
+		# username
+		$errmsg = i("Username cannot contain single (') or double (&quot;) quotes, less than (&lt;), or greater than (&gt;) and can be from 2 to 64 characters long");
+		$h .= labeledFormItem('username', i('Username'), 'text', '^([A-Za-z0-9-!@#$%^&\*\(\)_=\+\[\]{}\\\|:;,\./\?~` ]){2,30}$',
+		                      1, '', $errmsg, '', '', '200px'); 
+		# password
+		# todo make required for adding
+		$errmsg = i("Password must be at least 4 characters long");
+		$h .= labeledFormItem('password', i('Password'), 'password', '^.{4,256}$', 0, '', $errmsg, '', '', '200px'); 
+		# confirm password
+		$h .= labeledFormItem('password2', i('Confirm Password'), 'password', '', 0, '', '', '', '', '200px'); 
+		$h .= "<br>\n";
+		# dns server list
+		$ipreg = '(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)';
+		$reg = "^($ipreg,)*($ipreg)$";
+		$errmsg = i("Invalid IP address specified - must be a valid IPV4 address");
+		$h .= labeledFormItem('dnsservers', i('DNS Server(s)'), 'text', $reg, 0, '', $errmsg,
+		                      '', '', '300px'); 
+		# domain controllers list
+		$reg = "$hostbase(,$hostbase){0,4}";
+		$errmsg = i("Invalid Domain Controller specified. Must be comma delimited list of hostnames or IP addresses, with up to 5 allowed");
+		$h .= labeledFormItem('domaincontrollers', i('Domain Controller(s)'), 'text', $reg, 0, '', $errmsg,
+		                      '', '', '300px'); 
+		# login description
+		$h .= labeledFormItem('logindescription', i('Login Description'), 'textarea', '',
+		                      1, '', '', '', '', '300px'); 
+
+		$h .= "</div>\n"; # center
+		$h .= "</div>\n"; # addomaindlgcontent
+		$h .= "</div>\n"; # addeditdlgcontent
+
+		$h .= "<div id=\"addeditdlgerrmsg\" class=\"nperrormsg\"></div>\n";
+		$h .= "<div id=\"editdlgbtns\" align=\"center\">\n";
+		$h .= dijitButton('addeditbtn', "Confirm", "saveResource();");
+		$h .= dijitButton('', "Cancel", "dijit.byId('addeditdlg').hide();");
+		$h .= "</div>\n"; # editdlgbtns
+		$h .= "</div>\n"; # addeditdlg
+
+		$h .= "<div dojoType=dijit.Dialog\n";
+		$h .= "      id=\"groupingnote\"\n";
+		$h .= "      title=\"AD Domain Grouping\"\n";
+		$h .= "      duration=250\n";
+		$h .= "      draggable=true>\n";
+		$msg = i("Each AD Domain should be a member of an AD Domain resource group. The following dialog will allow you to add the new AD Domain to a group.");
+		$h .= wordwrap($msg, 75, '<br>');
+		$h .= "<br><br>\n";
+		$h .= "<div align=\"center\">\n";
+		$h .= dijitButton('', "Close", "dijit.byId('groupingnote').hide();");
+		$h .= "</div>\n"; # btn div
+		$h .= "</div>\n"; # groupingnote
+
+		$h .= "<div dojoType=dijit.Dialog\n";
+		$h .= "      id=\"groupdlg\"\n";
+		$h .= "      title=\"AD Domain Grouping\"\n";
+		$h .= "      duration=250\n";
+		$h .= "      draggable=true>\n";
+		$h .= "<div id=\"groupdlgcontent\"></div>\n";
+		$h .= "<div align=\"center\">\n";
+		$script  = "    dijit.byId('groupdlg').hide();\n";
+		$script .= "    checkFirstAdd();\n";
+		$h .= dijitButton('', "Close", $script);
+		$h .= "</div>\n"; # btn div
+		$h .= "</div>\n"; # groupdlg
+
+		return $h;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn validateResourceData()
+	///
+	/// \return array with these fields:\n
+	/// \b rscid - id of resource (from addomain table)\n
+	/// \b name\n
+	/// \b owner\n
+	/// \b times - array of arrays, each having 2 keys: start and end, each in
+	///    unix timestamp format\n
+	/// \b error - 0 if submitted data validates; 1 if anything is invalid\n
+	/// \b errormsg - if error = 1; string of error messages separated by html
+	///    break tags
+	///
+	/// \brief validates form input from editing or adding an AD domain
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function validateResourceData() {
+		global $user;
+
+		$return = array('error' => 0);
+		$errormsg = array();
+
+		$return['rscid'] = getContinuationVar('rscid', 0);
+		$return["name"] = processInputVar("name", ARG_STRING);
+		$return["owner"] = processInputVar("owner", ARG_STRING, "{$user["unityid"]}@{$user['affiliation']}");
+		$return["domaindnsname"] = processInputVar("domaindnsname", ARG_STRING);
+		$return["domainnetbiosname"] = processInputVar("domainnetbiosname", ARG_STRING);
+		$return["username"] = processInputVar("username", ARG_STRING);
+		$return["password"] = processInputVar("password", ARG_STRING);
+		$return["password2"] = processInputVar("password2", ARG_STRING);
+		$return["dnsservers"] = processInputVar("dnsservers", ARG_STRING);
+		$return["domaincontrollers"] = processInputVar("domaincontrollers", ARG_STRING);
+		$return["logindescription"] = processInputVar("logindescription", ARG_STRING);
+
+		$return['logindescription'] = preg_replace("/[\n\s]*$/", '', $return['logindescription']);
+		$return['logindescription'] = preg_replace("/\r/", '', $return['logindescription']);
+		$return['logindescription'] = htmlspecialchars($return['logindescription']);
+		$return['logindescription'] = preg_replace("/\n/", '<br>', $return['logindescription']);
+
+		if(! preg_match("/^([A-Za-z0-9-!@#$%^&\*\(\)_=\+\[\]{}\\\|:;,\.\/\?~` ]){2,30}$/", $return['name'])) {
+			$return['error'] = 1;
+			$errormsg[] = i("Name cannot contain single (') or double (&quot;) quotes, less than (&lt;), or greater than (&gt;) and can be from 2 to 30 characters long");
+		}
+		elseif($this->checkExistingField('name', $return['name'], $return['rscid'])) {
+			$return['error'] = 1;
+			$errormsg[] = i("An AD domain already exists with this name.");
+		}
+		if(! validateUserid($return['owner'])) {
+			$return['error'] = 1;
+			$errormsg[] = i("Submitted owner is not valid");
+		}
+
+		if(! preg_match('/^([A-Za-z0-9]{1,63})(\.[A-Za-z0-9-_]+)*(\.?[A-Za-z0-9])$/', $return['domaindnsname'])) {
+			$return['error'] = 1;
+			$errormsg[] = i("Domain DNS Name should be in the format domain.tld and can only contain letters, numbers, dashes(-), periods(.), and underscores(_) (e.g. myuniversity.edu)");
+		}
+		if(! preg_match('/^[a-zA-Z0-9_][-a-zA-Z0-9_\.]{0,14}$/', $return['domainnetbiosname'])) {
+			$return['error'] = 1;
+			$errormsg[] = i("Domain NetBIOS Name can only contain letters, numbers, dashes(-), periods(.), and underscores(_) and can be up to 15 characters long");
+		}
+
+		if(! preg_match('/^([A-Za-z0-9-!@#$%^&\*\(\)_=\+\[\]{}\\\|:;,\.\/\?~` ]){2,30}$/', $return['username'])) {
+			$return['error'] = 1;
+			$errormsg[] = i("Username cannot contain single (') or double (&quot;) quotes, less than (&lt;), or greater than (&gt;) and can be from 2 to 64 characters long");
+		}
+
+		if((! empty($return['password']) ||
+		   ! empty($return['password2'])) &&
+		   ! preg_match('/^.{4,256}$/', $return['password'])) {
+			$return['error'] = 1;
+			$errormsg[] = i("Password must be at least 4 characters long");
+		}
+		elseif($return['password'] != $return['password2']) {
+			$return['error'] = 1;
+			$errormsg[] = i("Passwords do not match");
+		}
+
+		$ips = explode(',', $return['dnsservers']);
+		foreach($ips as $ip) {
+			if(! validateIPv4addr($ip)) {
+				$return['error'] = 1;
+				$errormsg[] = i("Invalid IP address specified for DNS Server - must be a valid IPV4 address");
+				break;
+			}
+		}
+
+		$dcs = explode(',', $return['domaincontrollers']);
+		if(count($dcs) > 5) {
+			$return['error'] = 1;
+			$errormsg[] = i("Too many Domain Controllers specified, up to 5 are allowed");
+		}
+		else {
+			foreach($dcs as $dc) {
+				if(! validateHostname($dc) && ! validateIPv4addr($dc)) {
+					$return['error'] = 1;
+					$errormsg[] = i("Invalid Domain Controller specified. Must be comman delimited list of hostnames or IP addresses, with up to 5 allowed");
+				}
+			}
+		}
+
+		/*if(! preg_match('/^$/', $return['logindescription'])) {
+			$return['error'] = 1;
+			$errormsg[] = i("");
+		}*/
+
+		if($return['error'])
+			$return['errormsg'] = implode('<br>', $errormsg);
+
+		return $return;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn checkResourceInUse($rscid)
+	///
+	/// \param $rscid - id of AD domain
+	///
+	/// \return empty string if not being used; string of where resource is
+	/// being used if being used
+	///
+	/// \brief checks to see if AD domain is being used
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function checkResourceInUse($rscid) {
+		$msg = '';
+
+		$query = "SELECT i.prettyname "
+		       . "FROM imageaddomain ia, "
+		       .      "image i "
+		       . "WHERE ia.addomainid = $rscid AND "
+		       .       "ia.imageid = i.id";
+		$qh = doQuery($query);
+		$images = array();
+		while($row = mysql_fetch_assoc($qh))
+			$images[] = $row['prettyname'];
+		if(count($images))
+			$msg = "This AD Domain cannot be deleted because the following <strong>images</strong> are using it:<br><br>\n" . implode("<br>\n", $images);
+
+		return $msg;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
+	/// \fn checkResourceInUse($rscid)
+	///
+	/// \param $rscid - id of resource
+	///
+	/// \return empty string if not being used; string of where resource is
+	/// being used if being used
+	///
+	/// \brief checks to see if resource is being used
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function checkResourceInUse($rscid) {
+		$msgs = array();
+
+		/*
+		# check reservations
+		$query = "SELECT rq.end "
+		       . "FROM request rq, "
+		       .      "reservation rs "
+		       . "WHERE rs.requestid = rq.id AND "
+		       .       "rs.imageid = $rscid AND "
+		       .       "rq.stateid NOT IN (1, 12) AND "
+		       .       "rq.end > NOW() "
+		       . "ORDER BY rq.end DESC "
+		       . "LIMIT 1";
+		$qh = doQuery($query);
+		if($row = mysql_fetch_assoc($qh))
+			$msgs[] = sprintf(i("There is at least one <strong>reservation</strong> for this image. The latest end time is %s."), prettyDatetime($row['end'], 1));;
+
+		# check blockComputers
+		$query = "SELECT br.name, "
+		       .        "bt.end " 
+		       . "FROM blockRequest br, " 
+		       .      "blockTimes bt, "
+		       .      "blockComputers bc "
+		       . "WHERE bc.imageid = $rscid AND "
+		       .       "bc.blockTimeid = bt.id AND "
+		       .       "bt.blockRequestid = br.id AND "
+		       .       "bt.end > NOW() AND "
+		       .       "bt.skip = 0 AND "
+		       .       "br.status = 'accepted' "
+		       . "ORDER BY bt.end DESC "
+		       . "LIMIT 1";
+		$qh = doQuery($query);
+		if($row = mysql_fetch_assoc($qh))
+			$msgs[] = sprintf(i("There is at least one <strong>Block Allocation</strong> with computers currently allocated with this image. Block Allocation %s has the latest end time which is %s."), $row['name'], prettyDatetime($row['end'], 1));
+
+		if(empty($msgs))
+			return '';
+
+		$msg = i("The selected AD Domain is currently being used in the following ways and cannot be deleted at this time.") . "<br><br>\n";
+		$msg .= implode("<br><br>\n", $msgs) . "<br><br>\n";
+		return $msg;
+		*/
+		return '';
+	}
+}
+
+?>

Modified: vcl/trunk/web/.ht-inc/image.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/image.php?rev=1762935&r1=1762934&r2=1762935&view=diff
==============================================================================
--- vcl/trunk/web/.ht-inc/image.php (original)
+++ vcl/trunk/web/.ht-inc/image.php Fri Sep 30 16:56:45 2016
@@ -87,13 +87,23 @@ class Image extends Resource {
 				$w = 12;
 				break;
 			case 'os':
-				$w = 7;
+				$w = 8;
+				break;
+			case 'addomain':
+				$w = 10;
+				break;
+			case 'baseOU':
+				$w = 12;
+				break;
+			case 'adauthenabled':
+				$w = 9;
 				break;
 			default:
 				return '';
 		}
 		if(preg_match('/MSIE/i', $_SERVER['HTTP_USER_AGENT']) ||
-		   preg_match('/Trident/i', $_SERVER['HTTP_USER_AGENT']))
+		   preg_match('/Trident/i', $_SERVER['HTTP_USER_AGENT']) ||
+		   preg_match('/Edge/i', $_SERVER['HTTP_USER_AGENT']))
 			$w = round($w * 11.5) . 'px';
 		else
 			$w = "{$w}em";
@@ -143,6 +153,12 @@ class Image extends Resource {
 				return i("Admin. Access");
 			case 'sethostname':
 				return i("Set Hostname");
+			case 'adauthenabled':
+				return i("Use AD Authentication");
+			case 'addomain':
+				return i("AD Domain");
+			case 'baseOU':
+				return i("Base OU");
 		}
 		return i(ucfirst($field));
 	}
@@ -434,6 +450,25 @@ class Image extends Resource {
 			$h .= "id=\"connectmethodids\">\n";
 		}
 		$h .= "</div>\n"; #labeledform
+
+		# AD authentication
+		$h .= "<div class=\"boxedoptions hidden\" id=\"imageadauthbox\">\n";
+		# enable toggle
+		$vals = getUserResources(array('addomainAdmin'), array("manageGroup"));
+		$extra = array();
+		if(count($vals['addomain']) == 0)
+			$extra['disabled'] = 'true';
+		$extra['onChange'] = 'toggleADauth();';
+		$h .= labeledFormItem('adauthenable', i('Use AD Authentication'), 'check', '', '', '', '', '', $extra);
+		# AD domain
+		$disabled = array('disabled' => 'true');
+		$h .= labeledFormItem('addomainid', i('AD Domain'), 'select', $vals['addomain'], '', '', '', '', $disabled);
+		# base OU
+		$reg = '^([Oo][Uu])=[^,]+(,([Oo][Uu])=[^,]+)*$';
+		$errmsg = i("Invalid base OU; do not include DC components");
+		$h .= labeledFormItem('baseou', i('Base OU'), 'text', $reg, 0, '', $errmsg, '', $disabled, '230px', helpIcon('baseouhelp')); 
+		$h .= "</div>\n"; # boxedoptions
+
 		# subimages
 		if(! $add) {
 			$h .= "<br>\n";
@@ -488,6 +523,8 @@ class Image extends Resource {
 		$h .= dijitButton('', i("Cancel"), $script);
 		$h .= "   </div>\n";
 		$h .= "</div>\n"; # autoconfirmdlg
+
+		$h .= helpTooltip('baseouhelp', i('OU where nodes deployed with this image will be registered. Do not enter the domain component (ex OU=Computers,OU=VCL)'));
 		return $h;
 	}
 
@@ -758,6 +795,41 @@ class Image extends Resource {
 			       . " WHERE id = {$data['imageid']}";
 			doQuery($query);
 		}
+
+		# ad authentication
+		if($olddata['ostype'] == 'windows') {
+			if($data['adauthenabled'] != $olddata['adauthenabled']) {
+				if($data['adauthenabled']) {
+					$esc_baseou = mysql_real_escape_string($data['baseou']);
+					$query = "INSERT INTO imageaddomain "
+					       .        "(imageid, "
+					       .        "addomainid, "
+					       .        "baseOU) "
+					       . "VALUES "
+					       .        "({$data['imageid']}, "
+					       .        "{$data['addomainid']}, "
+					       .        "'$esc_baseou')";
+					doQuery($query);
+				}
+				else {
+					$query = "DELETE FROM imageaddomain "
+					       . "WHERE imageid = {$data['imageid']}";
+					doQuery($query);
+				}
+			}
+			elseif($data['adauthenabled'] &&
+			       ($data['addomainid'] != $olddata['addomainid'] ||
+			       $data['baseou'] != $olddata['baseOU'])) {
+				$esc_baseou = mysql_real_escape_string($data['baseou']);
+				$query = "UPDATE imageaddomain "
+				       . "SET addomainid = {$data['addomainid']}, "
+				       .     "baseOU = '$esc_baseou' "
+				       . "WHERE imageid = {$data['imageid']}";
+				doQuery($query);
+			}
+		}
+
+		# imagemeta
 		if(empty($olddata['imagemetaid']) &&
 		   ($data['checkuser'] == 0 || $data['rootaccess'] == 0 ||
 		   ($olddata['ostype'] == 'windows' && $data['sethostname'] == 1) ||
@@ -1148,6 +1220,20 @@ class Image extends Resource {
 		       .         "{$data['basedoffrevisionid']})";
 		doQuery($query, 205);
 		$imageid = dbLastInsertID();
+
+		# ad authentication
+		if($data['adauthenabled']) {
+			$esc_baseou = mysql_real_escape_string($data['baseou']);
+			$query = "INSERT INTO imageaddomain "
+			       .        "(imageid, "
+			       .        "addomainid, "
+			       .        "baseOU) "
+			       . "VALUES "
+			       .        "($imageid, "
+			       .        "{$data['addomainid']}, "
+			       .        "'$esc_baseou')";
+			doQuery($query);
+		}
 	
 		// possibly add entry to imagemeta table
 		$imagemetaid = 0;
@@ -1530,6 +1616,9 @@ class Image extends Resource {
 		$return["sethostname"] = processInputVar("sethostname", ARG_NUMERIC);
 		$return["sysprep"] = processInputVar("sysprep", ARG_NUMERIC); # only in add
 		$return["connectmethodids"] = processInputVar("connectmethodids", ARG_STRING); # only in add
+		$return["adauthenabled"] = processInputVar("adauthenabled", ARG_NUMERIC);
+		$return["addomainid"] = processInputVar("addomainid", ARG_NUMERIC);
+		$return["baseou"] = processInputVar("baseou", ARG_STRING);
 
 		$return['requestid'] = getContinuationVar('requestid'); # only in add
 		$return["imageid"] = getContinuationVar('imageid');
@@ -1636,6 +1725,27 @@ class Image extends Resource {
 			$return['error'] = 1;
 			$errormsg[] = i("Use Sysprep must be Yes or No");
 		}
+		if($return['adauthenabled'] != 0 && $return['adauthenabled'] != 1)
+			$return['adauthenabled'] = 0;
+		if($return['adauthenabled'] == 1) {
+			$vals = getUserResources(array('addomainAdmin'), array("manageGroup"));
+			if(! array_key_exists($return['addomainid'], $vals['addomain'])) {
+				$return['error'] = 1;
+				$errormsg[] = i("Invalid AD Domain submitted");
+			}
+			if(! preg_match('/^([Oo][Uu])=[^,]+(,([Oo][Uu])=[^,]+)*$/', $return['baseou'])) {
+				$return['error'] = 1;
+				$errormsg[] = i("Invalid Base OU submitted, must start with OU=");
+			}
+			if(preg_match('/DC=.+(,DC=.+)*$/', $return['baseou'])) {
+				$return['error'] = 1;
+				$errormsg[] = i("Base OU must not contain DC= components");
+			}
+		}
+		else {
+			$return['addomainid'] = 0;
+			$return['baseou'] = NULL;
+		}
 		if(empty($return['desc'])) {
 			$return['error'] = 1;
 			$errormsg[] = i("You must include a description of the image") . "<br>";
@@ -1659,6 +1769,7 @@ class Image extends Resource {
 				$return['connectmethodids'] = implode(',', array_keys($ids));
 			}
 		}
+
 		if($return['error'])
 			$return['errormsg'] = implode('<br>', $errormsg);
 		return $return;

Modified: vcl/trunk/web/.ht-inc/resource.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/resource.php?rev=1762935&r1=1762934&r2=1762935&view=diff
==============================================================================
--- vcl/trunk/web/.ht-inc/resource.php (original)
+++ vcl/trunk/web/.ht-inc/resource.php Fri Sep 30 16:56:45 2016
@@ -43,6 +43,9 @@ function resource($type) {
 		case 'schedule':
 			$obj = new Schedule();
 			break;
+		case 'addomain':
+			$obj = new ADdomain();
+			break;
 	}
 
 	$html = $obj->selectionText();
@@ -69,7 +72,7 @@ class Resource {
 	var $deletetoggled;
 	var $errmsg;
 	var $namefield;
-	var $noadd;
+	var $addable;
 	var $jsondata;
 
 	/////////////////////////////////////////////////////////////////////////////
@@ -288,7 +291,7 @@ class Resource {
 		if($this->deletetoggled) {
 			$h .= "<label for=\"showdeleted\"><strong>";
 			$h .= i("Include Deleted {$this->restypename}s:");
-			$h .= "</strong>:</label>\n";
+			$h .= "</strong></label>\n";
 			$h .= "<input type=\"checkbox\" dojoType=\"dijit.form.CheckBox\" ";
 			$h .= "id=\"showdeleted\" onChange=\"resource.GridFilter();\">\n";
 		}
@@ -306,7 +309,8 @@ class Resource {
 		$h .= "<thead>\n";
 		$h .= "<tr>\n";
 		if(preg_match('/MSIE/i', $_SERVER['HTTP_USER_AGENT']) ||
-		   preg_match('/Trident/i', $_SERVER['HTTP_USER_AGENT']))
+		   preg_match('/Trident/i', $_SERVER['HTTP_USER_AGENT']) ||
+		   preg_match('/Edge/i', $_SERVER['HTTP_USER_AGENT']))
 			$w = array('64px', '38px', '200px');
 		else
 			$w = array('5em', '3.5em', '17em');
@@ -442,7 +446,8 @@ class Resource {
 				return '';
 		}
 		if(preg_match('/MSIE/i', $_SERVER['HTTP_USER_AGENT']) ||
-		   preg_match('/Trident/i', $_SERVER['HTTP_USER_AGENT']))
+		   preg_match('/Trident/i', $_SERVER['HTTP_USER_AGENT']) ||
+		   preg_match('/Edge/i', $_SERVER['HTTP_USER_AGENT']))
 			$w = round($w * 11.5) . 'px';
 		else
 			$w = "{$w}em";
@@ -1602,6 +1607,34 @@ class Resource {
 
 	/////////////////////////////////////////////////////////////////////////////
 	///
+	/// \fn checkExistingField($field, $value, $id=0)
+	///
+	/// \param $field - database field name
+	/// \param $value - value for $field
+	/// \param $id - (optional, default=0) if nonzero, ignore resource with this
+	/// id
+	///
+	/// \return 1 if existing resource with $field set to $value, 0 if not
+	///
+	/// \brief checks to see if there is already a record in the database with
+	/// $field set to $value
+	///
+	/////////////////////////////////////////////////////////////////////////////
+	function checkExistingField($field, $value, $id=0) {
+		$query = "SELECT id FROM {$this->restype} "
+		       . "WHERE `$field` = '$value'";
+		if($this->deletetoggled)
+			$query .= " AND deleted = 0";
+		if($id)
+			$query .= " AND id != $id";
+		$qh = doQuery($query);
+		if(mysql_num_rows($qh))
+			return 1;
+		return 0;
+	}
+
+	/////////////////////////////////////////////////////////////////////////////
+	///
 	/// \fn extraSelectAdminOptions()
 	///
 	/// \return html
@@ -1617,6 +1650,8 @@ class Resource {
 	///
 	/// \fn checkResourceInUse($rscid)
 	///
+	/// \param $rscid - id of resource
+	///
 	/// \return empty string if not being used; string of where resource is
 	/// being used if being used
 	///

Modified: vcl/trunk/web/.ht-inc/states.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/states.php?rev=1762935&r1=1762934&r2=1762935&view=diff
==============================================================================
--- vcl/trunk/web/.ht-inc/states.php (original)
+++ vcl/trunk/web/.ht-inc/states.php Fri Sep 30 16:56:45 2016
@@ -56,6 +56,7 @@ $actions["entry"] = array('main',
                           'computer',
                           'managementnode',
                           'schedule',
+                          'addomain',
                           'RESTresourceBasic',
                           'RESTresourceDetail',
                           #'testDojoREST',
@@ -596,6 +597,8 @@ $actions['mode']['managementnode'] = "re
 $actions['args']['managementnode'] = 'managementnode';
 $actions['mode']['schedule'] = "resource";
 $actions['args']['schedule'] = 'schedule';
+$actions['mode']['addomain'] = "resource";
+$actions['args']['addomain'] = 'addomain';
 $actions['mode']['viewResources'] = "viewResources";
 $actions['mode']['jsonResourceStore'] = "jsonResourceStore";
 $actions['mode']['AJpromptToggleDeleteResource'] = "AJpromptToggleDeleteResource";
@@ -656,6 +659,7 @@ $actions['pages']['image'] = "image";
 $actions['pages']['computer'] = "computer";
 $actions['pages']['managementnode'] = "managementnode";
 $actions['pages']['schedule'] = "schedule";
+$actions['pages']['addomain'] = "addomain";
 $actions['pages']['viewResources'] = "resource";
 $actions['pages']['jsonResourceStore'] = "resource";
 $actions['pages']['AJpromptToggleDeleteResource'] = "resource";

Modified: vcl/trunk/web/.ht-inc/utils.php
URL: http://svn.apache.org/viewvc/vcl/trunk/web/.ht-inc/utils.php?rev=1762935&r1=1762934&r2=1762935&view=diff
==============================================================================
--- vcl/trunk/web/.ht-inc/utils.php (original)
+++ vcl/trunk/web/.ht-inc/utils.php Fri Sep 30 16:56:45 2016
@@ -300,6 +300,7 @@ function initGlobals() {
 		case 'computer':
 		case 'managementnode':
 		case 'schedule':
+		case 'addomain':
 			require_once(".ht-inc/resource.php");
 			break;
 		case 'storebackend':
@@ -1326,15 +1327,20 @@ function getImages($includedeleted=0, $i
 	       .        "i.lastupdate, "
 	       .        "i.forcheckout, "
 	       .        "i.maxinitialtime, "
-	       .        "i.imagemetaid "
-	       . "FROM image i, "
-	       .      "platform p, "
+	       .        "i.imagemetaid, "
+	       .        "ad.id AS addomainid, "
+	       .        "ad.name AS addomain, "
+	       .        "iadd.baseOU "
+	       . "FROM platform p, "
 	       .      "OS o, "
 	       .      "OStype ot, "
 	       .      "resource r, "
 	       .      "resourcetype t, "
 	       .      "user u, "
-	       .      "affiliation a "
+	       .      "affiliation a, "
+	       .      "image i "
+	       . "LEFT JOIN imageaddomain iadd ON (i.id = iadd.imageid) "
+	       . "LEFT JOIN addomain ad ON (iadd.addomainid = ad.id) "
 	       . "WHERE i.platformid = p.id AND "
 	       .       "r.resourcetypeid = t.id AND "
 	       .       "t.name = 'image' AND "
@@ -1357,6 +1363,9 @@ function getImages($includedeleted=0, $i
 			$imagelist[$includedeleted][$row['id']]['sethostname'] = 0;
 		else
 			$imagelist[$includedeleted][$row['id']]['sethostname'] = 1;
+		$imagelist[$includedeleted][$row['id']]['adauthenabled'] = 0;
+		if($row['addomainid'] != NULL)
+			$imagelist[$includedeleted][$row['id']]['adauthenabled'] = 1;
 		if($row["imagemetaid"] != NULL) {
 			if(array_key_exists($row['imagemetaid'], $allmetadata)) {
 				$metaid = $row['imagemetaid'];
@@ -2998,22 +3007,31 @@ function getResourceGroupMembers($type="
 		$orders = "m.hostname";
 		$types = "'managementnode'";
 	}
+	elseif($type == "addomain") {
+		$names = "ad.name AS addomain ";
+		$joins = "LEFT JOIN addomain ad ON (r.subid = ad.id AND r.resourcetypeid = 19) ";
+		$orders = "ad.name";
+		$types = "'addomain'";
+	}
 	else {
 		$names = "c.hostname AS computer, "
 		       . "c.deleted, "
 		       . "i.prettyname AS image, "
 		       . "i.deleted AS deleted2, "
 		       . "s.name AS schedule, "
-		       . "m.hostname AS managementnode ";
+		       . "m.hostname AS managementnode, "
+		       . "ad.name AS addomain ";
 		$joins = "LEFT JOIN computer c ON (r.subid = c.id AND r.resourcetypeid = 12) "
 		       . "LEFT JOIN image i ON (r.subid = i.id AND r.resourcetypeid = 13) "
 		       . "LEFT JOIN schedule s ON (r.subid = s.id AND r.resourcetypeid = 15) "
-		       . "LEFT JOIN managementnode m ON (r.subid = m.id AND r.resourcetypeid = 16) ";
+		       . "LEFT JOIN managementnode m ON (r.subid = m.id AND r.resourcetypeid = 16) "
+		       . "LEFT JOIN addomain ad ON (r.subid = ad.id AND r.resourcetypeid = 19) ";
 		$orders = "c.hostname, "
 		        . "i.prettyname, "
 		        . "s.name, "
-		        . "m.hostname";
-		$types = "'computer','image','schedule','managementnode'";
+		        . "m.hostname, "
+		        . "ad.name";
+		$types = "'computer','image','schedule','managementnode','addomain'";
 	}
 
 	$query = "SELECT rgm.resourcegroupid, "
@@ -8813,6 +8831,60 @@ function getNATports($resid) {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn getADdomains($addomainid=0)
+///
+/// \param $addomainid - (optional) id of an AD domain
+///
+/// \return array of available AD domains with the following keys:\n
+/// \b id\n
+/// \b resourceid\n
+/// \b name\n
+/// \b ownerid\n
+/// \b owner\n
+/// \b domaindnsname\n
+/// \b domainnetbiosname\n
+/// \b username\n
+/// \b dnsservers\n
+/// \b domaincontrollers\n
+/// \b logindescription
+///
+/// \brief builds an array of AD domains
+///
+////////////////////////////////////////////////////////////////////////////////
+function getADdomains($addomainid=0) {
+	$query = "SELECT ad.id, "
+	       .        "r.id AS resourceid, "
+	       .        "ad.name, "
+	       .        "ad.ownerid, "
+	       .        "CONCAT(u.unityid, '@', a.name) AS owner, "
+	       .        "ad.domainDNSName AS domaindnsname, "
+	       .        "ad.domainNetBIOSName AS domainnetbiosname, "
+	       .        "ad.username, "
+	       .        "ad.dnsServers AS dnsservers, "
+	       .        "ad.domainControllers AS domaincontrollers, "
+	       .        "ad.logindescription "
+	       . "FROM addomain ad, "
+	       .      "affiliation a, "
+	       .      "user u, "
+	       .      "resource r, "
+	       .      "resourcetype rt "
+	       . "WHERE ad.ownerid = u.id AND "
+	       .       "u.affiliationid = a.id AND "
+	       .       "r.subid = ad.id AND "
+	       .       "r.resourcetypeid = rt.id AND "
+	       .       "rt.name = 'addomain'";
+	if($addomainid)
+		$query .= " AND ad.id = $addomainid";
+
+	$qh = doQuery($query);
+	$addomainlist = array();
+	while($row = mysql_fetch_assoc($qh))
+		$addomainlist[$row['id']] = $row;
+	return $addomainlist;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn getBlockTimeData($start, $end)
 ///
 /// \param $start - (optional) start time of blockTimes to get in unix timestamp
@@ -9098,11 +9170,12 @@ function labeledFormItem($id, $label, $t
 		$required = 'false';
 	switch($type) {
 		case 'text':
+		case 'password':
 			if($width == '')
 				$width = '300px';
 			$h .= "<label for=\"$id\">$label:</label>\n";
 			$h .= "<span class=\"labeledform\">\n";
-			$h .= "<input type=\"text\" ";
+			$h .= "<input type=\"$type\" ";
 			$h .=        "dojoType=\"dijit.form.ValidationTextBox\" ";
 			$h .=        "required=\"$required\" ";
 			if($constraints != '')
@@ -11891,6 +11964,26 @@ function validateIPv4addr($ip) {
 
 ////////////////////////////////////////////////////////////////////////////////
 ///
+/// \fn validateHostname($name)
+///
+/// \param $name  the hostname to validate
+///
+/// \return 1 if valid hostname else 0
+///
+/// \brief validates a given hostname
+///
+////////////////////////////////////////////////////////////////////////////////
+function validateHostname($name) {
+	if(strlen($name) > 255)
+		return 0;
+	// return 0 if dotted numbers only
+	if(preg_match('/^[0-9]+(\.[0-9]+){3}$/', $name))
+		return 0;
+	return preg_match('/^([A-Za-z0-9]{1,63})(\.[A-Za-z0-9-_]+)*(\.?[A-Za-z0-9])$/', $name);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+///
 /// \fn validateEmailAddress($addr)
 ///
 /// \param $addr - an email address
@@ -12714,6 +12807,12 @@ function getNavMenuData($homeurl=HOMEURL
 		$menudata['vm']['selected'] = checkMenuItemSelected('vm');
 	}
 
+	if(in_array("addomainAdmin", $user["privileges"])) {
+		$menudata['addomain']['url'] = BASEURL . SCRIPT . "?mode=addomain";
+		$menudata['addomain']['title'] = i('AD Domains');
+		$menudata['addomain']['selected'] = checkMenuItemSelected('addomain');
+	}
+
 	if(checkUserHasPerm('Schedule Site Maintenance')) {
 		$menudata['sitemaintenance']['url'] = BASEURL . SCRIPT . "?mode=siteMaintenance";
 		$menudata['sitemaintenance']['title'] = i('Site Maintenance');
@@ -13277,6 +13376,9 @@ function getDojoHTML($refresh) {
 				case 'computer':
 					$jsfile = 'resources/computer.js';
 					break;
+				case 'addomain':
+					$jsfile = 'resources/addomain.js';
+					break;
 			}
 			if(isset($jsfile))
 				$rt .= "<script type=\"text/javascript\" src=\"js/$jsfile?v=$v\"></script>\n";
@@ -13366,6 +13468,9 @@ function getDojoHTML($refresh) {
 				case 'computer':
 					$jsfile = 'resources/computer.js';
 					break;
+				case 'addomain':
+					$jsfile = 'resources/addomain.js';
+					break;
 			}
 			$rt .= "<script type=\"text/javascript\" src=\"js/resources.js?v=$v\"></script>\n";
 			if(isset($jsfile))

Modified: vcl/trunk/web/css/vcl.css
URL: http://svn.apache.org/viewvc/vcl/trunk/web/css/vcl.css?rev=1762935&r1=1762934&r2=1762935&view=diff
==============================================================================
--- vcl/trunk/web/css/vcl.css (original)
+++ vcl/trunk/web/css/vcl.css Fri Sep 30 16:56:45 2016
@@ -591,6 +591,14 @@ body {
 	width: 16.5em;
 }
 
+#addomaindlgcontent label {
+	width: 14.5em;
+}
+
+#addomaindlgcontent .labeledform {
+	margin-left: 15em;
+}
+
 #cfgvartypelbl {
 	margin-top: 6px;
 }

Added: vcl/trunk/web/js/resources/addomain.js
URL: http://svn.apache.org/viewvc/vcl/trunk/web/js/resources/addomain.js?rev=1762935&view=auto
==============================================================================
--- vcl/trunk/web/js/resources/addomain.js (added)
+++ vcl/trunk/web/js/resources/addomain.js Fri Sep 30 16:56:45 2016
@@ -0,0 +1,145 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+function ADdomain() {
+	Resource.apply(this, Array.prototype.slice.call(arguments));
+	this.restype = 'addomain';
+}
+ADdomain.prototype = new Resource();
+
+ADdomain.prototype.colformatter = function(value, rowIndex, obj) {
+	if(obj.field == 'logindescription') {
+		var str = value.replace(/&lt;br>/g, '<br>'); 
+		str = str.replace(/&amp;/g, '&'); 
+		return str;
+	}
+	return value;
+}
+
+var resource = new ADdomain();
+
+function addNewResource(title) {
+	dijit.byId('addeditdlg').set('title', title);
+	dijit.byId('addeditbtn').set('label', title);
+	dojo.byId('editresid').value = 0;
+	resetEditResource();
+	dijit.byId('addeditdlg').show();
+}
+
+function inlineEditResourceCB(data, ioArgs) {
+	if(data.items.status == 'success') {
+		dojo.byId('saveresourcecont').value = data.items.cont;
+		dijit.byId('addeditdlg').set('title', data.items.title);
+		dijit.byId('addeditbtn').set('label', 'Save Changes');
+		dojo.byId('editresid').value = data.items.rscid;
+		dijit.byId('name').set('value', data.items.data.name);
+		dijit.byId('owner').set('value', data.items.data.owner);
+
+		dijit.byId('domaindnsname').set('value', data.items.data.domaindnsname);
+		dijit.byId('domainnetbiosname').set('value', data.items.data.domainnetbiosname);
+		dijit.byId('username').set('value', data.items.data.username);
+		dijit.byId('dnsservers').set('value', data.items.data.dnsservers);
+		dijit.byId('domaincontrollers').set('value', data.items.data.domaincontrollers);
+		dijit.byId('logindescription').set('value', data.items.data.logindescription);
+
+		dojo.byId('addeditdlgerrmsg').innerHTML = '';
+		dijit.byId('addeditdlg').show();
+	}
+	else if(data.items.status == 'noaccess') {
+		alert('Access denied to edit this item');
+	}
+}
+
+function resetEditResource() {
+	var fields = ['name', 'owner', 'domaindnsname', 'domainnetbiosname', 'username', 'password', 'password2', 'dnsservers', 'domaincontrollers', 'logindescription'];
+	for(var i = 0; i < fields.length; i++) {
+		dijit.byId(fields[i]).reset();
+	}
+	dojo.byId('addeditdlgerrmsg').innerHTML = '';
+}
+
+function saveResource() {
+	var errobj = dojo.byId('addeditdlgerrmsg');
+	var fields = ['name', 'owner', 'domaindnsname', 'domainnetbiosname', 'username', 'password', 'password2', 'dnsservers', 'domaincontrollers', 'logindescription'];
+
+	if(dojo.byId('editresid').value == 0)
+		var data = {continuation: dojo.byId('addresourcecont').value};
+	else
+		var data = {continuation: dojo.byId('saveresourcecont').value};
+
+	for(var i = 0; i < fields.length; i++) {
+		if(! checkValidatedObj(fields[i], errobj))
+			return;
+		data[fields[i]] = dijit.byId(fields[i]).get('value');
+	}
+	if(dijit.byId('password').get('value') != dijit.byId('password2').get('value')) {
+		dojo.byId('addeditdlgerrmsg').innerHTML = _('Passwords do not match');
+		return;
+	}
+
+	dijit.byId('addeditbtn').set('disabled', true);
+	RPCwrapper(data, saveResourceCB, 1);
+}
+
+function saveResourceCB(data, ioArgs) {
+	if(data.items.status == 'error') {
+		dojo.byId('addeditdlgerrmsg').innerHTML = '<br>' + data.items.msg;
+		dijit.byId('addeditbtn').set('disabled', false);
+		return;
+	}
+	else if(data.items.status == 'adderror') {
+		alert(data.items.errormsg);
+	}
+	else if(data.items.status == 'success') {
+		if(data.items.action == 'add') {
+			if(typeof resourcegrid !== 'undefined') {
+				resourcegrid.store.newItem(data.items.data);
+				resourcegrid.sort();
+			}
+			dojo.forEach(dijit.findWidgets(dojo.byId('groupdlgcontent')), function(w) {
+				w.destroyRecursive();
+			});
+			if(data.items.nogroups == 0) {
+				dojo.byId('groupdlgcontent').innerHTML = data.items.groupingHTML;
+				AJdojoCreate('groupdlgcontent');
+				dojo.byId('resources').value = data.items.data.id;
+				populateLists('resources', 'ingroups', 'inresourcename', 'outresourcename', 'resgroupinggroupscont');
+				dijit.byId('groupdlg').show();
+				dijit.byId('groupingnote').show();
+			}
+		}
+		else {
+			resourcegrid.store.fetch({
+				query: {id: data.items.data.id},
+				onItem: function(item) {
+					var fields = ['name', 'owner', 'domaindnsname', 'domainnetbiosname', 'username','dnsservers', 'domaincontrollers', 'logindescription'];
+					for(var i = 0; i < fields.length; i++) {
+						dijit.byId(fields[i]).reset();
+						resourcegrid.store.setValue(item, fields[i], data.items.data[fields[i]]);
+					}
+				},
+				onComplete: function(items, result) {
+					// when call resourcegrid.sort directly, the table contents disappear; not sure why
+					setTimeout(function() {resourcegrid.sort();}, 10);
+				}
+			});
+		}
+		dijit.byId('addeditdlg').hide();
+		resetEditResource();
+		setTimeout(function() {dijit.byId('addeditbtn').set('disabled', false);}, 250);
+	}
+}

Modified: vcl/trunk/web/js/resources/image.js
URL: http://svn.apache.org/viewvc/vcl/trunk/web/js/resources/image.js?rev=1762935&r1=1762934&r2=1762935&view=diff
==============================================================================
--- vcl/trunk/web/js/resources/image.js (original)
+++ vcl/trunk/web/js/resources/image.js Fri Sep 30 16:56:45 2016
@@ -29,13 +29,16 @@ Image.prototype.colformatter = function(
 	   obj.field == 'forcheckout' ||
 	   obj.field == 'checkuser' ||
 	   obj.field == 'rootaccess' ||
-	   obj.field == 'sethostname') {
+	   obj.field == 'sethostname' ||
+	   obj.field == 'adauthenabled') {
 		if(value == "0")
 			return '<span class="rederrormsg">' + _('false') + '</span>';
 		if(value == "1")
 			return '<span class="ready">' + _('true') + '</span>';
 	}
-	if(obj.field == 'maxinitialtime' && value == 0)
+	if((obj.field == 'maxinitialtime' && value == 0) ||
+	   (obj.field == 'addomain' && value == null) ||
+	   (obj.field == 'baseOU' && value == null))
 		return '(unset)';
 	return value;
 }
@@ -70,6 +73,25 @@ function inlineEditResourceCB(data, ioAr
 			dojo.addClass('sethostnamediv', 'hidden');
 		dojo.byId('connectmethodlist').innerHTML = data.items.data.connectmethods.join('<br>');
 		dijit.byId('connectmethodttd').set('href', data.items.data.connectmethodurl);
+		if(data.items.data.ostype == 'windows') {
+			dojo.removeClass('imageadauthbox', 'hidden');
+			if(data.items.data.adauthenabled) {
+				dijit.byId('adauthenable').set('checked', true);
+				dijit.byId('addomainid').set('value', data.items.data.addomainid);
+				dijit.byId('baseou').set('value', data.items.data.baseOU);
+			}
+			else {
+				dijit.byId('adauthenable').set('checked', false);
+				dijit.byId('addomainid').reset();
+				dijit.byId('baseou').reset();
+			}
+		}
+		else {
+			dojo.addClass('imageadauthbox', 'hidden');
+			dijit.byId('adauthenable').set('checked', false);
+			dijit.byId('addomainid').reset();
+			dijit.byId('baseou').reset();
+		}
 		dijit.byId('subimagedlg').set('href', data.items.data.subimageurl);
 		dojo.byId('revisiondiv').innerHTML = data.items.data.revisionHTML;
 		dojo.byId('addeditdlgerrmsg').innerHTML = '';
@@ -111,6 +133,9 @@ function resetEditResource() {
 		dijit.byId('advancedoptions').toggle();
 	dojo.byId('connectmethodlist').innerHTML = '';
 	dojo.byId('addeditdlgerrmsg').innerHTML = '';
+	dijit.byId('adauthenable').reset();
+	dijit.byId('addomainid').reset();
+	dijit.byId('baseou').reset();
 }
 
 function saveResource() {
@@ -168,6 +193,12 @@ function saveResource() {
 		setTimeout(function() {dijit.byId('reload').focus();}, 300);
 		return;
 	}
+	if(dijit.byId('adauthenable').checked && ! checkValidatedObj('baseou', errobj)) {
+		if(! dijit.byId('advancedoptions').open)
+			dijit.byId('advancedoptions').toggle();
+		setTimeout(function() {dijit.byId('baseou').focus();}, 300);
+		return;
+	}
 
 	if(dojo.byId('editresid').value == 0)
 		var data = {continuation: dojo.byId('addresourcecont').value};
@@ -234,6 +265,16 @@ function saveResource() {
 	data['cpuspeed'] = dijit.byId('cpuspeed').get('value');
 	if(dijit.byId('reload'))
 		data['reload'] = dijit.byId('reload').get('value');
+	if(dijit.byId('adauthenable').checked) {
+		data['adauthenabled'] = 1;
+		data['addomainid'] = dijit.byId('addomainid').get('value');
+		data['baseou'] = dijit.byId('baseou').get('value');
+	}
+	else {
+		data['adauthenabled'] = 0;
+		data['addomainid'] = 0;
+		data['baseou'] = '';
+	}
 
 	submitbtn.set('disabled', true);
 	RPCwrapper(data, saveResourceCB, 1);
@@ -287,6 +328,10 @@ function saveResourceCB(data, ioArgs) {
 					resourcegrid.store.setValue(item, 'rootaccess', parseInt(data.items.data.rootaccess));
 					resourcegrid.store.setValue(item, 'sethostname', parseInt(data.items.data.sethostname));
 					resourcegrid.store.setValue(item, 'reloadtime', data.items.data.reloadtime);
+					resourcegrid.store.setValue(item, 'adauthenabled', data.items.data.adauthenabled);
+					resourcegrid.store.setValue(item, 'addomainid', data.items.data.addomainid);
+					resourcegrid.store.setValue(item, 'addomain', data.items.data.addomain);
+					resourcegrid.store.setValue(item, 'baseOU', data.items.data.baseOU);
 				},
 				onComplete: function(items, result) {
 					// when call resourcegrid.sort directly, the table contents disappear; not sure why
@@ -663,10 +708,17 @@ function startImageCB(data, ioArgs) {
 	}
 	else
 		dojo.addClass('sethostnamediv', 'hidden');
-	if(data.items.ostype == 'windows')
+	dijit.byId('adauthenable').set('checked', false);
+	dijit.byId('addomainid').reset();
+	dijit.byId('baseou').reset();
+	if(data.items.ostype == 'windows') {
 		dojo.removeClass('sysprepdiv', 'hidden');
-	else
+		dojo.removeClass('imageadauthbox', 'hidden');
+	}
+	else {
 		dojo.addClass('sysprepdiv', 'hidden');
+		dojo.addClass('imageadauthbox', 'hidden');
+	}
 
 	if(data.items.checkpoint) {
 		dojo.addClass('imageendrescontent', 'hidden');
@@ -763,3 +815,14 @@ function submitUpdateImageClickthroughCB
 	dijit.byId('clickthroughDlgBtn').set('disabled', false);
 	resRefresh();
 }
+
+function toggleADauth() {
+	if(dijit.byId('adauthenable').checked) {
+		dijit.byId('addomainid').set('disabled', false);
+		dijit.byId('baseou').set('disabled', false);
+	}
+	else {
+		dijit.byId('addomainid').set('disabled', true);
+		dijit.byId('baseou').set('disabled', true);
+	}
+}

Modified: vcl/trunk/web/themes/dropdownmenus/css/theme.css
URL: http://svn.apache.org/viewvc/vcl/trunk/web/themes/dropdownmenus/css/theme.css?rev=1762935&r1=1762934&r2=1762935&view=diff
==============================================================================
--- vcl/trunk/web/themes/dropdownmenus/css/theme.css (original)
+++ vcl/trunk/web/themes/dropdownmenus/css/theme.css Fri Sep 30 16:56:45 2016
@@ -144,6 +144,27 @@ div.dijitDialog h3 {
 	background-color: black;
 	color: white;
 }
+#renameDialog tbody tr:hover td,
+#renameDialog tbody tr td {
+	background-color: transparent;
+	border-top: none;
+	padding: 0.2em;
+}
+div.dijitDialog h2,
+div.dijitDialog h3 {
+	margin-top: 0.1em;
+}
+#confdelcontent h3 {
+	font-size: 1.4em;
+	font-weight: bold;
+}
+#groupbyresourcediv table.dojoxGridRowTable tbody tr:hover td {
+	background-color: transparent;
+}
+#groupbyresourcediv div.dojoxGridRowSelected table.dojoxGridRowTable tbody tr td {
+	background-color: black;
+	color: white;
+}
 #addeditdlg tbody tr td.dijitButtonNode {
 	border: 1px solid #c0c0c0;
 }
@@ -416,6 +437,12 @@ h1.site-title {
 #advancedoptions .labeledform {
     margin-left: 21.5em;
 }
+#imageadauthbox label {
+    width: 16em;
+}
+#imageadauthbox .labeledform {
+    margin-left: 16.5em;
+}
 #mgmtnodedlgcontent label {
     width: 24.5em;
 }