azkaban-uncached

proxy user lock down

3/4/2013 3:36:00 PM

Details

diff --git a/src/java/azkaban/execapp/FlowRunner.java b/src/java/azkaban/execapp/FlowRunner.java
index eebb427..fbbedc0 100644
--- a/src/java/azkaban/execapp/FlowRunner.java
+++ b/src/java/azkaban/execapp/FlowRunner.java
@@ -5,6 +5,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.BlockingQueue;
@@ -33,6 +34,7 @@ import azkaban.executor.ExecutorLoader;
 import azkaban.executor.ExecutorManagerException;
 import azkaban.flow.FlowProps;
 import azkaban.jobtype.JobTypeManager;
+import azkaban.project.Project;
 import azkaban.project.ProjectLoader;
 import azkaban.project.ProjectManagerException;
 import azkaban.user.Permission;
@@ -82,7 +84,7 @@ public class FlowRunner extends EventHandler implements Runnable {
 	private boolean flowFinished = false;
 	private boolean flowCancelled = false;
 	
-	private List<String> proxyUsers = null;
+	private HashSet<String> proxyUsers = null;
 	
 	private boolean proxyUserLockDown = false;
 	
@@ -102,23 +104,18 @@ public class FlowRunner extends EventHandler implements Runnable {
 		this.proxyUserLockDown = doLockDown;
 	}
 	
-	private List<String> getProxyUsers() {
-		List<String> allUsers = new ArrayList<String>();
-		allUsers.add(flow.getSubmitUser());
-		List<Triple<String, Boolean, Permission>> permissions;
+	private HashSet<String> getProxyUsers() {
+		HashSet<String> proxyUsers = null;
+
 		try {
-			permissions = projectLoader.getProjectPermissions(flow.getProjectId());
-			for(Triple<String, Boolean, Permission> triple : permissions) {
-				if(triple.getSecond() == false && (triple.getThird().isPermissionSet(Permission.Type.EXECUTE) || triple.getThird().isPermissionSet(Permission.Type.ADMIN) )) {
-					allUsers.add(triple.getFirst());
-				}
-			}
+			Project project = projectLoader.fetchProjectById(flow.getProjectId());
+			proxyUsers = project.getProxyUsers();
 		} catch (ProjectManagerException e) {
 			// This gets funny when no user specified and submitted by the scheduler
 			logger.error("Failed to get project permission from project. Using default permission.", e);
 		}
 		
-		return allUsers;
+		return proxyUsers;
 	}
 
 	public FlowRunner setGlobalProps(Props globalProps) {
diff --git a/src/java/azkaban/execapp/JobRunner.java b/src/java/azkaban/execapp/JobRunner.java
index ad03089..9448dd1 100644
--- a/src/java/azkaban/execapp/JobRunner.java
+++ b/src/java/azkaban/execapp/JobRunner.java
@@ -17,6 +17,7 @@ package azkaban.execapp;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.HashSet;
 import java.util.List;
 
 import org.apache.log4j.Appender;
@@ -63,11 +64,11 @@ public class JobRunner extends EventHandler implements Runnable {
 	private Object syncObject = new Object();
 	
 	private final JobTypeManager jobtypeManager;
-	private List<String> proxyUsers = null;
+	private HashSet<String> proxyUsers = null;
 	
 	private boolean userLockDown;
 
-	public JobRunner(ExecutableNode node, Props props, File workingDir, List<String> proxyUsers, ExecutorLoader loader, JobTypeManager jobtypeManager, Logger flowLogger) {
+	public JobRunner(ExecutableNode node, Props props, File workingDir, HashSet<String> proxyUsers, ExecutorLoader loader, JobTypeManager jobtypeManager, Logger flowLogger) {
 		this.props = props;
 		this.node = node;
 		this.workingDir = workingDir;
@@ -226,11 +227,8 @@ public class JobRunner extends EventHandler implements Runnable {
 				props.put(AbstractProcessJob.WORKING_DIR, workingDir.getAbsolutePath());
 			}
 			
-			String jobProxyUser = props.getString("user.to.proxy", null);
-			if(jobProxyUser == null) {
-				jobProxyUser = proxyUsers.get(0);
-			}
-			else {
+			if(props.containsKey("user.to.proxy")) {
+				String jobProxyUser = props.getString("user.to.proxy");
 				if(! proxyUsers.contains(jobProxyUser)) {
 					logger.error("User " + jobProxyUser + " has no permission to execute this job " + node.getJobId() + "!");
 					if(userLockDown) {
@@ -238,7 +236,6 @@ public class JobRunner extends EventHandler implements Runnable {
 					}
 				}
 			}
-			props.put("user.to.proxy", jobProxyUser);
 			
 			//job = JobWrappingFactory.getJobWrappingFactory().buildJobExecutor(node.getJobId(), props, logger);
 			try {
diff --git a/src/java/azkaban/project/JdbcProjectLoader.java b/src/java/azkaban/project/JdbcProjectLoader.java
index 6f0c3f5..631b4e1 100644
--- a/src/java/azkaban/project/JdbcProjectLoader.java
+++ b/src/java/azkaban/project/JdbcProjectLoader.java
@@ -14,6 +14,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
@@ -27,6 +28,7 @@ import org.apache.log4j.Logger;
 
 import azkaban.flow.Flow;
 import azkaban.project.ProjectLogEvent.EventType;
+import azkaban.scheduler.JdbcScheduleLoader.EncodingType;
 import azkaban.user.Permission;
 import azkaban.user.User;
 import azkaban.utils.DataSourceUtils;
@@ -236,11 +238,11 @@ public class JdbcProjectLoader implements ProjectLoader {
 			throw new ProjectManagerException("Checking for existing project failed. " + name, e);
 		}
 		
-		final String INSERT_PROJECT = "INSERT INTO projects ( name, active, modified_time, create_time, version, last_modified_by, description) values (?,?,?,?,?,?,?)";
+		final String INSERT_PROJECT = "INSERT INTO projects ( name, active, modified_time, create_time, version, last_modified_by, description, enc_type, settings_blob) values (?,?,?,?,?,?,?,?,?)";
 		// Insert project
 		try {
 			long time = System.currentTimeMillis();
-			int i = runner.update(connection, INSERT_PROJECT, name, true, time, time, null, creator.getUserId(), description);
+			int i = runner.update(connection, INSERT_PROJECT, name, true, time, time, null, creator.getUserId(), description, defaultEncodingType.numVal, null);
 			if (i == 0) {
 				throw new ProjectManagerException("No projects have been inserted.");
 			}
@@ -504,6 +506,49 @@ public class JdbcProjectLoader implements ProjectLoader {
 		}
 	}
 	
+	
+	
+	@Override
+	public void updateProjectSettings(Project project) throws ProjectManagerException {
+		Connection connection = getConnection();
+		try {
+			updateProjectSettings(connection, project, defaultEncodingType);
+			connection.commit();
+		}
+		catch (SQLException e) {
+			throw new ProjectManagerException("Error updating project settings", e);
+		}
+		finally {
+			DbUtils.closeQuietly(connection);
+		}
+	}
+	
+	private void updateProjectSettings(Connection connection, Project project, EncodingType encType) throws ProjectManagerException {
+		QueryRunner runner = new QueryRunner();
+		final String UPDATE_PROJECT_SETTINGS = "UPDATE projects SET enc_type=?, settings_blob=? WHERE id=?";
+		
+		String json = JSONUtils.toJSON(project.toObject());
+		byte[] data = null;
+		try {
+			byte[] stringData = json.getBytes("UTF-8");
+			data = stringData;
+	
+			if (encType == EncodingType.GZIP) {
+				data = GZIPUtils.gzipBytes(stringData);
+			}
+			logger.debug("NumChars: " + json.length() + " UTF-8:" + stringData.length + " Gzip:"+ data.length);
+		} catch(IOException e) {
+			throw new ProjectManagerException("Failed to encode. ", e);
+		}
+
+		try {
+			runner.update(connection, UPDATE_PROJECT_SETTINGS, encType.numVal, data, project.getId());
+			connection.commit();
+		} catch (SQLException e) {
+			throw new ProjectManagerException("Error updating project " + project.getName() + " version " + project.getVersion(), e);
+		}
+	}
+	
 	@Override
 	public void removePermission(Project project, String name, boolean isGroup) throws ProjectManagerException {
 		QueryRunner runner = new QueryRunner(dataSource);
@@ -942,13 +987,13 @@ public class JdbcProjectLoader implements ProjectLoader {
 	
 	private static class ProjectResultHandler implements ResultSetHandler<List<Project>> {
 		private static String SELECT_PROJECT_BY_ID = 
-				"SELECT id, name, active, modified_time, create_time, version, last_modified_by, description FROM projects WHERE id=?";
+				"SELECT id, name, active, modified_time, create_time, version, last_modified_by, description, enc_type, settings_blob FROM projects WHERE id=?";
 		
 		private static String SELECT_ALL_ACTIVE_PROJECTS = 
-				"SELECT id, name, active, modified_time, create_time, version, last_modified_by, description FROM projects WHERE active=true";
+				"SELECT id, name, active, modified_time, create_time, version, last_modified_by, description, enc_type, settings_blob FROM projects WHERE active=true";
 		
 		private static String SELECT_ACTIVE_PROJECT_BY_NAME = 
-				"SELECT id, name, active, modified_time, create_time, version, last_modified_by, description FROM projects WHERE name=? AND active=true";
+				"SELECT id, name, active, modified_time, create_time, version, last_modified_by, description, enc_type, settings_blob FROM projects WHERE name=? AND active=true";
 		
 		@Override
 		public List<Project> handle(ResultSet rs) throws SQLException {
@@ -966,8 +1011,35 @@ public class JdbcProjectLoader implements ProjectLoader {
 				int version = rs.getInt(6);
 				String lastModifiedBy = rs.getString(7);
 				String description = rs.getString(8);
+				int encodingType = rs.getInt(9);
+				byte[] data = rs.getBytes(10);
+				
+				Project project;
+				if (data != null) {
+					EncodingType encType = EncodingType.fromInteger(encodingType);
+					Object blobObj;
+					try {
+						// Convoluted way to inflate strings. Should find common package or helper function.
+						if (encType == EncodingType.GZIP) {
+							// Decompress the sucker.
+							String jsonString = GZIPUtils.unGzipString(data, "UTF-8");
+							blobObj = JSONUtils.parseJSONFromString(jsonString);
+						}
+						else {
+							String jsonString = new String(data, "UTF-8");
+							blobObj = JSONUtils.parseJSONFromString(jsonString);
+						}	
+						project = Project.projectFromObject(blobObj);
+					} catch (IOException e) {
+						throw new SQLException("Failed to get project.", e);
+					}
+				}
+				else {
+					project = new Project(id, name);
+				}
+				
+				// update the fields as they may have changed
 				
-				Project project = new Project(id, name);
 				project.setActive(active);
 				project.setLastModifiedTimestamp(modifiedTime);
 				project.setCreateTimestamp(createTime);
diff --git a/src/java/azkaban/project/Project.java b/src/java/azkaban/project/Project.java
index 5a611fc..f7b32b6 100644
--- a/src/java/azkaban/project/Project.java
+++ b/src/java/azkaban/project/Project.java
@@ -18,6 +18,7 @@ package azkaban.project;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -27,6 +28,7 @@ import azkaban.user.Permission;
 import azkaban.user.Permission.Type;
 import azkaban.user.User;
 import azkaban.utils.Pair;
+import azkaban.utils.Props;
 
 public class Project {
 	private final int id;
@@ -41,6 +43,15 @@ public class Project {
 	private LinkedHashMap<String, Permission> userPermissionMap = new LinkedHashMap<String, Permission>();
 	private LinkedHashMap<String, Permission> groupPermissionMap = new LinkedHashMap<String, Permission>();
 	private Map<String, Flow> flows = null;
+	private HashSet<String> proxyUsers = new HashSet<String>();
+	
+	public HashSet<String> getProxyUsers() {
+		return proxyUsers;
+	}
+	
+	public void setProxyUsers(HashSet<String> proxyUsers) {
+		this.proxyUsers = proxyUsers;
+	}
 	
 	public Project(int id, String name) {
 		this.id = id;
@@ -235,8 +246,13 @@ public class Project {
 			userMap.put("permissions", entry.getValue().toStringArray());
 			users.add(userMap);
 		}
-
+		
 		projectObject.put("users", users);
+		
+
+		ArrayList<String> proxyUserList = new ArrayList<String>(proxyUsers);
+		projectObject.put("proxyUsers", proxyUserList);
+		
 		return projectObject;
 	}
 
@@ -277,6 +293,10 @@ public class Project {
 
 			project.setUserPermission(userid, perm);
 		}
+		
+		List<String> proxyUserList = (List<String>) projectObject.get("proxyUsers");
+		HashSet<String> proxyUsers = new HashSet<String>(proxyUserList);
+		project.setProxyUsers(proxyUsers);
 
 		return project;
 	}
@@ -390,4 +410,27 @@ public class Project {
 	public void setVersion(int version) {
 		this.version = version;
 	}
+
+	public List<String> getProxyUserList() {
+		return new ArrayList<String>(proxyUsers);
+	}
+
+//	public Object getSettingsObject() {
+//		HashMap<String, Object> projectObject = new HashMap<String, Object>();
+//		ArrayList<String> proxyUserList = new ArrayList<String>(proxyUsers);
+//		projectObject.put("proxyUsers", proxyUserList);
+//		
+//		return projectObject;
+//	}
+//	
+//	@SuppressWarnings("unchecked")
+//	public static HashSet<String> proxyUserFromSettingsObj(Object object) {
+//		Map<String, Object> settingsObj = (Map<String, Object>) object;
+//
+//		List<String> proxyUserList = (List<String>) settingsObj.get("proxyUsers");
+//		HashSet<String> proxyUsers = new HashSet<String>(proxyUserList);
+//		
+//		return proxyUsers;
+//	}
+
 }
diff --git a/src/java/azkaban/project/ProjectLoader.java b/src/java/azkaban/project/ProjectLoader.java
index 5d01a71..f1b04ed 100644
--- a/src/java/azkaban/project/ProjectLoader.java
+++ b/src/java/azkaban/project/ProjectLoader.java
@@ -2,6 +2,7 @@ package azkaban.project;
 
 import java.io.File;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
@@ -221,5 +222,7 @@ public interface ProjectLoader {
 	Props fetchProjectProperty(int projectId, int projectVer, String propsName) throws ProjectManagerException;
 
 	List<Triple<String, Boolean, Permission>> getProjectPermissions(int projectId) throws ProjectManagerException;
+
+	void updateProjectSettings(Project project) throws ProjectManagerException;
 	
 }
\ No newline at end of file
diff --git a/src/java/azkaban/project/ProjectManager.java b/src/java/azkaban/project/ProjectManager.java
index e526474..d4ac2fb 100644
--- a/src/java/azkaban/project/ProjectManager.java
+++ b/src/java/azkaban/project/ProjectManager.java
@@ -217,6 +217,10 @@ public class ProjectManager {
 		return;
 	}
 
+	public void updateProjectSetting(Project project) throws ProjectManagerException {
+		projectLoader.updateProjectSettings(project);		
+	}
+	
 	public void updateProjectPermission(Project project, String name, Permission perm, boolean group, User modifier) throws ProjectManagerException {
 		logger.info("User " + modifier.getUserId() + " updating permissions for project " + project.getName() + " for " + name + " " + perm.toString());
 		projectLoader.updatePermission(project, name, perm, group);
@@ -329,4 +333,6 @@ public class ProjectManager {
 	public void postProjectEvent(Project project, EventType type, String user,String message) {
 		projectLoader.postEvent(project, type, user, message);
 	}
+
+
 }
diff --git a/src/java/azkaban/user/UserManager.java b/src/java/azkaban/user/UserManager.java
index 114ccbe..e027c25 100644
--- a/src/java/azkaban/user/UserManager.java
+++ b/src/java/azkaban/user/UserManager.java
@@ -58,4 +58,6 @@ public interface UserManager {
 	 * @return
 	 */
 	public Role getRole(String roleName);
+
+	public boolean validateProxyUser(String proxyUser, String realUser);
 }
diff --git a/src/java/azkaban/user/XmlUserManager.java b/src/java/azkaban/user/XmlUserManager.java
index 09539b5..ab1fbe8 100644
--- a/src/java/azkaban/user/XmlUserManager.java
+++ b/src/java/azkaban/user/XmlUserManager.java
@@ -19,6 +19,7 @@ package azkaban.user;
 import java.io.File;
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.HashSet;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -53,6 +54,7 @@ public class XmlUserManager implements UserManager {
 	public static final String USERNAME_ATTR = "username";
 	public static final String PASSWORD_ATTR = "password";
 	public static final String ROLES_ATTR = "roles";
+	public static final String PROXY_ATTR = "proxy";
 	public static final String GROUPS_ATTR = "groups";
 
 	private String xmlPath;
@@ -60,6 +62,7 @@ public class XmlUserManager implements UserManager {
 	private HashMap<String, User> users;
 	private HashMap<String, String> userPassword;
 	private HashMap<String, Role> roles;
+	private HashMap<String, HashSet<String>> proxyUserMap;
 
 	/**
 	 * The constructor.
@@ -81,6 +84,7 @@ public class XmlUserManager implements UserManager {
 		HashMap<String, User> users = new HashMap<String, User>();
 		HashMap<String, String> userPassword = new HashMap<String, String>();
 		HashMap<String, Role> roles = new HashMap<String, Role>();
+		HashMap<String, HashSet<String>> proxyUserMap = new HashMap<String, HashSet<String>>();
 
 		// Creating the document builder to parse xml.
 		DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory
@@ -113,7 +117,7 @@ public class XmlUserManager implements UserManager {
 			Node node = azkabanUsersList.item(i);
 			if (node.getNodeType() == Node.ELEMENT_NODE) {
 				if (node.getNodeName().equals(USER_TAG)) {
-					parseUserTag(node, users, userPassword);
+					parseUserTag(node, users, userPassword, proxyUserMap);
 				}
 				else if (node.getNodeName().equals(ROLE_TAG)) {
 					parseRoleTag(node, roles);
@@ -126,10 +130,11 @@ public class XmlUserManager implements UserManager {
 			this.users = users;
 			this.userPassword = userPassword;
 			this.roles = roles;
+			this.proxyUserMap = proxyUserMap;
 		}
 	}
 
-	private void parseUserTag(Node node, HashMap<String, User> users, HashMap<String, String> userPassword) {
+	private void parseUserTag(Node node, HashMap<String, User> users, HashMap<String, String> userPassword, HashMap<String, HashSet<String>> proxyUserMap) {
 		NamedNodeMap userAttrMap = node.getAttributes();
 		Node userNameAttr = userAttrMap.getNamedItem(USERNAME_ATTR);
 		if (userNameAttr == null) {
@@ -157,6 +162,22 @@ public class XmlUserManager implements UserManager {
 				user.addRole(role);
 			}
 		}
+		
+		Node proxy = userAttrMap.getNamedItem(PROXY_ATTR);
+		if (proxy != null) {
+			String value = proxy.getNodeValue();
+			String[] groupSplit = value.split("\\s*,\\s*");
+			for (String group : groupSplit) {
+				if(proxyUserMap.containsKey(username)) {
+					proxyUserMap.get(username).add(group);
+				}
+				else {
+					HashSet<String> proxySet = new HashSet<String>();
+					proxySet.add(group);
+					proxyUserMap.put(username, proxySet);
+				}
+			}
+		}
 
 		Node groups = userAttrMap.getNamedItem(GROUPS_ATTR);
 		if (groups != null) {
@@ -248,4 +269,14 @@ public class XmlUserManager implements UserManager {
 		// Return true. Validation should be added when groups are added to the xml.
 		return true;
 	}
+
+	@Override
+	public boolean validateProxyUser(String proxyUser, String realUser) {
+		if(proxyUserMap.containsKey(realUser) && proxyUserMap.get(realUser).contains(proxyUser)) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
 }
diff --git a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
index 2f1403b..328b6d6 100644
--- a/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
+++ b/src/java/azkaban/webapp/servlet/ProjectManagerServlet.java
@@ -28,6 +28,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -36,6 +37,7 @@ import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.swing.text.StyledEditorKit.BoldAction;
 
 import org.apache.commons.fileupload.FileItem;
 import org.apache.commons.io.FileUtils;
@@ -212,6 +214,11 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 				ajaxAddPermission(project, ret, req, user);
 			}
 		}
+		else if (ajaxName.equals("addProxyUser")) {
+			if (handleAjaxPermission(project, user, Type.ADMIN, ret)) {
+				ajaxAddProxyUser(project, ret, req, user);
+			}
+		}
 		else if (ajaxName.equals("fetchFlowExecutions")) {
 			if (handleAjaxPermission(project, user, Type.READ, ret)) {
 				ajaxFetchFlowExecutions(project, ret, req);
@@ -546,6 +553,55 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 		ret.put("nodes", nodeList);
 	}
 	
+	private void ajaxAddProxyUser(Project project, HashMap<String, Object> ret, HttpServletRequest req, User user) throws ServletException {
+		String name = getParam(req, "name");
+		
+		logger.info("Adding proxy user " + name + " by " + user.getUserId());
+		
+
+		
+		boolean doProxy = Boolean.parseBoolean(getParam(req, "doProxy"));
+		
+		
+		HashSet<String> proxyUsers = project.getProxyUsers();
+		//add
+		if(doProxy) {			
+			if(proxyUsers.contains(name)) {
+				return;
+			}
+			else {
+				if(userManager.validateProxyUser(name, user.getUserId())) {
+					proxyUsers.add(name);
+				}
+				else {
+					ret.put("error", "User " + user.getUserId() + " has no permission to add " + name + " as proxy user for project " + project.getName());
+					return;
+				}
+			}
+		}
+		else {
+			if(!proxyUsers.contains(name)) {
+				return;
+			}
+			else {
+				if(userManager.validateProxyUser(name, user.getUserId())) {
+					proxyUsers.remove(name);
+				}
+				else {
+					ret.put("error", "User " + user.getUserId() + " has no permission to remove " + name + " as proxy user for project " + project.getName());
+					return;
+				}
+			}
+		}
+		try {
+			projectManager.updateProjectSetting(project);
+		} catch (ProjectManagerException e) {
+			// TODO Auto-generated catch block
+			ret.put("error", e.getMessage());
+		}
+		
+	}
+	
 	private void ajaxAddPermission(Project project, HashMap<String, Object> ret, HttpServletRequest req, User user) throws ServletException {
 		String name = getParam(req, "name");
 		boolean group = Boolean.parseBoolean(getParam(req, "group"));
@@ -792,6 +848,7 @@ public class ProjectManagerServlet extends LoginAbstractAzkabanServlet {
 
 				page.add("permissions", project.getUserPermissions());
 				page.add("groupPermissions", project.getGroupPermissions());
+				page.add("proxyUsers", project.getProxyUserList());
 				
 				if(hasPermission(project, user, Type.ADMIN)) {
 					page.add("isAdmin", true);
diff --git a/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm b/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
index 159bd05..9b13af6 100644
--- a/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
+++ b/src/java/azkaban/webapp/servlet/velocity/permissionspage.vm
@@ -71,6 +71,7 @@
 					#if($isAdmin)
 						<button id="addUser" class="btn1">Add User</button>
 						<button id="addGroup" class="btn1">Add Group</button>
+						<button id="addProxyUser" class="btn2">Add Proxy User</button>
 					#end
 				</div>
 
@@ -93,7 +94,7 @@
 					<th class="tb-read">Read</th>
 					<th class="tb-write">Write</th>
 					<th class="tb-execute">Execute</th>
-					<th class="tb-schedule">Schedule</th>
+					<th class="tb-schedule">Schedule</th>					
 					#if($isAdmin)
 						<th class="tb-action"></th>
 					#end
@@ -172,6 +173,45 @@
 #end
 			</tbody>
 		</table>
+		
+		<br></br>
+		<table id="proxy-user-table" class="all-jobs permission-table">
+			<thead>
+				<tr>
+					<th class="tb-username">Proxy User</th>
+					<th class="tb-perm">Admin</th>
+					<th class="tb-read">Read</th>
+					<th class="tb-write">Write</th>
+					<th class="tb-execute">Execute</th>
+					<th class="tb-schedule">Schedule</th>
+					<th class="tb-proxy">Proxy</th>
+					#if($isAdmin)
+						<th class="tb-action"></th>
+					#end
+				</tr>
+			</thead>
+			<tbody>
+#if($proxyUsers)
+#foreach($proxyUser in $proxyUsers)
+	<tr id="${proxyUser}-row" >
+		<td class="tb-name">#if($proxyUser == $username) ${proxyUser} <span class="sublabel">(you)</span> #else $proxyUser #end</td>
+			<td><input id="proxy-${proxyUser}-admin-checkbox" type="checkbox" name="admin" disabled="disabled" ></input></td>
+			<td><input id="proxy-${proxyUser}-read-checkbox" type="checkbox" name="read" disabled="disabled" ></input></td>
+			<td><input id="proxy-${proxyUser}-write-checkbox" type="checkbox" name="write" disabled="disabled" ></input></td>
+			<td><input id="proxy-${proxyUser}-execute-checkbox" type="checkbox" name="execute" disabled="disabled" ></input></td>
+			<td><input id="proxy-${proxyUser}-schedule-checkbox" type="checkbox" name="schedule" disabled="disabled" ></input></td>
+			<td><input id="proxy-${proxyUser}-proxy-checkbox" type="checkbox" name="proxy" disabled="disabled" checked="true"></input></td>
+
+		#if($isAdmin)
+			<td><button id="proxy-${proxyUser}" class="change-btn btn2">Change</button></td>
+		#end
+	</tr>
+#end
+#else
+	<tr><td class="last">No Proxy User Found.</td></tr>
+#end
+			</tbody>
+		</table>
 #end
 
 		</div>
@@ -184,6 +224,7 @@
 					<dl>
 						<dt>User</dt>
 						<dd><input id="user-box" name="userid" type="text" /></dd>
+					<div id="otherCheckBoxes">
 						<dt class="nextline">Admin</dt>
 						<dd><input id="admin-change" name="admin" type="checkbox" /></dd>
 						<dt>Read</dt>
@@ -194,6 +235,11 @@
 					    <dd><input id="execute-change" name="execute" type="checkbox" /></dd>
 					    <dt>Schedule</dt>
 					    <dd><input id="schedule-change" name="schedule" type="checkbox" /></dd>
+					</div>
+					<div id="proxyCheckBox" hidden=true>
+				    	<dt>Proxy</dt>
+				    	<dd><input id="proxy-change" name="proxy" type="checkbox" /></dd>
+				    </div>
 					</dl>
 				</fieldset>
 			</div>
diff --git a/src/sql/create_project_table.sql b/src/sql/create_project_table.sql
index e52fa76..34115a7 100644
--- a/src/sql/create_project_table.sql
+++ b/src/sql/create_project_table.sql
@@ -7,6 +7,8 @@ CREATE TABLE projects (
 	version INT,
 	last_modified_by VARCHAR(64) NOT NULL,
 	description VARCHAR(255),
+	enc_type TINYINT,
+	settings_blob LONGBLOB,
 	UNIQUE INDEX project_id (id),
 	INDEX project_name (name)
 ) ENGINE=InnoDB;
diff --git a/src/sql/update_2.0_to_2.01.sql b/src/sql/update_2.0_to_2.01.sql
index cff37d2..493df5a 100644
--- a/src/sql/update_2.0_to_2.01.sql
+++ b/src/sql/update_2.0_to_2.01.sql
@@ -17,4 +17,7 @@ ALTER TABLE schedules ADD COLUMN schedule_options LONGBLOB;
 
 ALTER TABLE project_events MODIFY COLUMN message VARCHAR(512);
 
+ALTER TABLE projects ADD COLUMN enc_type TINYINT;
+ALTER TABLE projects ADD COLUMN settings_blob LONGBLOB;
+
 
diff --git a/src/web/js/azkaban.permission.view.js b/src/web/js/azkaban.permission.view.js
index 429b8ed..e04bc55 100644
--- a/src/web/js/azkaban.permission.view.js
+++ b/src/web/js/azkaban.permission.view.js
@@ -2,18 +2,20 @@ $.namespace('azkaban');
 
 var permissionTableView;
 var groupPermissionTableView;
+var proxyTableView;
 azkaban.PermissionTableView= Backbone.View.extend({
   events : {
 	"click button": "handleChangePermission"
   },
   initialize : function(settings) {
   	this.group = settings.group;
+  	this.proxy = settings.proxy;
   },
   render: function() {
   },
   handleChangePermission: function(evt) {
   	  var currentTarget = evt.currentTarget;
-  	  changePermissionView.display(currentTarget.id, false, this.group);
+  	  changePermissionView.display(currentTarget.id, false, this.group, this.proxy);
   }
 });
 
@@ -27,13 +29,22 @@ azkaban.ChangePermissionView= Backbone.View.extend({
   initialize : function(settings) {
   	$('#errorMsg').hide();
   },
-  display: function(userid, newPerm, group) {
+  display: function(userid, newPerm, group, proxy) {
   	// 6 is the length of the prefix "group-"
   	this.userid = group ? userid.substring(6, userid.length) : userid;
+  	if(group == true) {
+  		this.userid = userid.substring(6, userid.length)
+  	} else if (proxy == true) {
+  		this.userid = userid.substring(6, userid.length)
+  	} else {
+  		this.userid = userid
+  	}
+  	
   	this.permission = {};
 	$('#user-box').val(this.userid);
 	this.newPerm = newPerm;
 	this.group = group;
+	this.proxy = proxy;
 	
 	var prefix = userid;
 	var adminInput = $("#" + prefix + "-admin-checkbox");
@@ -41,12 +52,16 @@ azkaban.ChangePermissionView= Backbone.View.extend({
 	var writeInput = $("#" + prefix + "-write-checkbox");
 	var executeInput = $("#" + prefix + "-execute-checkbox");
 	var scheduleInput = $("#" + prefix + "-schedule-checkbox");
+	var proxyInput = $("#" + prefix + "-proxy-checkbox");
 	
 	if (newPerm) {
 		if (group) {
 			$('#change-title').text("Add New Group Permissions");
 		}
-		else {
+		else if(proxy){
+			$('#change-title').text("Add New Proxy User Permissions");
+		}
+		else{
 			$('#change-title').text("Add New User Permissions");
 		}
 		$('#user-box').attr("disabled", null);
@@ -57,22 +72,39 @@ azkaban.ChangePermissionView= Backbone.View.extend({
 		this.permission.write = false;
 		this.permission.execute = false;
 		this.permission.schedule = false;
+		this.doProxy = false;
+		
 	}
 	else {
 		if (group) {
 			$('#change-title').text("Change Group Permissions");
 		}
+		else if(proxy){
+			$('#change-title').text("Change Proxy User Permissions");
+			this.doProxy = $(proxyInput).attr("checked");
+		}
 		else {
 			$('#change-title').text("Change User Permissions");
 		}
 		
 		$('#user-box').attr("disabled", "disabled");
 		
+		
+		
 		this.permission.admin = $(adminInput).attr("checked");
 		this.permission.read = $(readInput).attr("checked");
 		this.permission.write = $(writeInput).attr("checked");
 		this.permission.execute = $(executeInput).attr("checked");
 		this.permission.schedule = $(scheduleInput).attr("checked");
+		this.doProxy = $(proxyInput).attr("checked");
+	}
+	
+	if(proxy) {
+		document.getElementById("otherCheckBoxes").hidden=true;
+		document.getElementById("proxyCheckBox").hidden=false;
+	} else {
+		document.getElementById("otherCheckBoxes").hidden=false;
+		document.getElementById("proxyCheckBox").hidden=true;
 	}
 	
 	this.changeCheckbox();
@@ -91,17 +123,27 @@ azkaban.ChangePermissionView= Backbone.View.extend({
             $("#errorMsg").hide();
           }
         });
+  	 
+
+  	 
   },
   render: function() {
   },
   handleCheckboxClick : function(evt) {
   	console.log("click");
   	var targetName = evt.currentTarget.name;
-  	this.permission[targetName] = evt.currentTarget.checked;
+  	if(targetName == "proxy") {
+  		this.doProxy = evt.currentTarget.checked;
+  	}
+  	else {
+  		this.permission[targetName] = evt.currentTarget.checked;
+  	}
   	this.changeCheckbox(evt);
   },
   changeCheckbox : function(evt) {
     var perm = this.permission;
+    var proxy = this.proxy;
+    var doProxy = this.doProxy;
 
   	if (perm.admin) {
   		$("#admin-change").attr("checked", true);
@@ -116,9 +158,13 @@ azkaban.ChangePermissionView= Backbone.View.extend({
   		
   		$("#schedule-change").attr("checked", true);
   		$("#schedule-change").attr("disabled", "disabled");
+  		
+  		$("#proxy-change").attr("checked", false);
+		$("#proxy-change").attr("disabled", "disabled");
   	}
   	else {
   		$("#admin-change").attr("checked", false);
+  		
   		$("#read-change").attr("checked", perm.read);
   		$("#read-change").attr("disabled", null);
   		  		
@@ -130,12 +176,16 @@ azkaban.ChangePermissionView= Backbone.View.extend({
   		
   		$("#schedule-change").attr("checked", perm.schedule);
 		$("#schedule-change").attr("disabled", null);
+		
+		$("#proxy-change").attr("checked", doProxy);
+		$("#proxy-change").attr("disabled", null);
+		
   	}
   	
   	$("#change-btn").removeClass("btn-disabled");
   	$("#change-btn").attr("disabled", null);
   	
-  	if (perm.admin || perm.read || perm.write || perm.execute || perm.schedule) {
+  	if (perm.admin || perm.read || perm.write || perm.execute || perm.schedule || doProxy) {
   		$("#change-btn").text("Commit");
   	}
   	else {
@@ -152,11 +202,14 @@ azkaban.ChangePermissionView= Backbone.View.extend({
   	var requestURL = contextURL + "/manager";
   	var name = $('#user-box').val();
 	var command = this.newPerm ? "addPermission" : "changePermission";
+	if(this.proxy) {
+		command = "addProxyUser";
+	}
 	var group = this.group;
 	
   	$.get(
 	      requestURL,
-	      {"project": projectName, "name": name, "ajax":command, "permissions": this.permission, "group": group},
+	      {"project": projectName, "name": name, "ajax":command, "permissions": this.permission, "doProxy": this.doProxy, "group": group},
 	      function(data) {
 	      	  console.log("Output");
 	      	  if (data.error) {
@@ -174,15 +227,21 @@ azkaban.ChangePermissionView= Backbone.View.extend({
 });
 
 $(function() {
-	permissionTableView = new azkaban.PermissionTableView({el:$('#permissions-table'), group: false});
-	groupPermissionTableView = new azkaban.PermissionTableView({el:$('#group-permissions-table'), group: true});
+	permissionTableView = new azkaban.PermissionTableView({el:$('#permissions-table'), group: false, proxy: false});
+	groupPermissionTableView = new azkaban.PermissionTableView({el:$('#group-permissions-table'), group: true, proxy: false});
+	proxyTableView = new azkaban.PermissionTableView({el:$('#proxy-user-table'), group: false, proxy: true});
 	changePermissionView = new azkaban.ChangePermissionView({el:$('#change-permission')});
 	
 	$('#addUser').bind('click', function() {
-		changePermissionView.display("", true, false);
+		changePermissionView.display("", true, false, false);
 	});
 	
 	$('#addGroup').bind('click', function() {
-		changePermissionView.display("", true, true);
+		changePermissionView.display("", true, true, false);
 	});
+	
+	$('#addProxyUser').bind('click', function() {
+		changePermissionView.display("", true, false, true);
+	});
+	
 });
diff --git a/unit/java/azkaban/test/execapp/JobRunnerTest.java b/unit/java/azkaban/test/execapp/JobRunnerTest.java
index 056cbac..e7f15a8 100644
--- a/unit/java/azkaban/test/execapp/JobRunnerTest.java
+++ b/unit/java/azkaban/test/execapp/JobRunnerTest.java
@@ -3,6 +3,7 @@ package azkaban.test.execapp;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 
 import junit.framework.Assert;
@@ -263,7 +264,7 @@ public class JobRunnerTest {
 		node.setExecutableFlow(flow);
 		
 		Props props = createProps(time, fail);
-		List<String> proxyUsers = new ArrayList<String>();
+		HashSet<String> proxyUsers = new HashSet<String>();
 		proxyUsers.add(flow.getSubmitUser());
 		JobRunner runner = new JobRunner(node, props, workingDir, proxyUsers, loader, jobtypeManager, logger);
 
diff --git a/unit/java/azkaban/test/execapp/MockProjectLoader.java b/unit/java/azkaban/test/execapp/MockProjectLoader.java
index 165f0c9..b10ea29 100644
--- a/unit/java/azkaban/test/execapp/MockProjectLoader.java
+++ b/unit/java/azkaban/test/execapp/MockProjectLoader.java
@@ -217,4 +217,11 @@ public class MockProjectLoader implements ProjectLoader {
 		// TODO Auto-generated method stub
 		return null;
 	}
+
+	@Override
+	public void updateProjectSettings(Project project)
+			throws ProjectManagerException {
+		// TODO Auto-generated method stub
+		
+	}
 }
\ No newline at end of file