From 9d2ba11e0918c9c3fa4c0d7470a472d035c5ad51 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 21 Sep 2011 21:26:00 +0000 Subject: [PATCH] - back end: extend the access control system for remote job submission and other operations. You can now designate a user as "manager" for a particular app. They can then: - control job-submit permissions for that app - deprecate/undeprecate versions of the app. - abort jobs for that app You can also designate a user a manage for the project. They can then edit permissions and quotas, as well as performing the app-specific functions for all apps. This is described here: http://boinc.berkeley.edu/trac/wiki/MultiUser#Accesscontrol This required some changes to the DB schema. svn path=/trunk/boinc/; revision=24250 --- checkin_notes | 39 ++ db/schema.sql | 508 +++++++++--------- html/inc/result.inc | 35 +- html/inc/submit.inc | 11 - html/inc/submit_util.inc | 86 +++ html/ops/db_update.php | 18 + html/user/manage.php | 70 +++ html/user/manage_app.php | 231 ++++++++ .../manage_project.php} | 91 ++-- html/user/submit.php | 24 +- html/user/submit_example.php | 2 +- py/Boinc/setup_project.py | 1 + tools/manage_privileges | 162 ++++++ 13 files changed, 928 insertions(+), 350 deletions(-) create mode 100644 html/inc/submit_util.inc create mode 100644 html/user/manage.php create mode 100644 html/user/manage_app.php rename html/{ops/submit_permissions.php => user/manage_project.php} (66%) create mode 100755 tools/manage_privileges diff --git a/checkin_notes b/checkin_notes index 8b6858b5d8..b22e72777b 100644 --- a/checkin_notes +++ b/checkin_notes @@ -6289,3 +6289,42 @@ David 20 Sept 2011 scheduler_op.cpp,h samples/example_app/ uc2.php + +David 21 Sept 2011 + - back end: extend the access control system for remote job submission + and other operations. + You can now designate a user as "manager" for a particular app. + They can then: + - control job-submit permissions for that app + - deprecate/undeprecate versions of the app. + - abort jobs for that app + + You can also designate a user a manage for the project. + They can then edit permissions and quotas, + as well as performing the app-specific functions for all apps. + + This is described here: + http://boinc.berkeley.edu/trac/wiki/MultiUser#Accesscontrol + + This required some changes to the DB schema. + + db/ + schema.sql + tools/ + manage_privileges + html/ + inc/ + submit.inc + submit_util.inc (new) + result.inc + ops/ + submit_permissions.php (removed) + db_update.php + user/ + manage_project.php (new) + submit_example.php + manage.php (new) + manage_app.php (new) + submit.php + py/Boinc/ + setup_project.py diff --git a/db/schema.sql b/db/schema.sql index 95fc87d8d6..3510ef8789 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -30,11 +30,11 @@ */ create table platform ( - id integer not null auto_increment, - create_time integer not null, - name varchar(254) not null, - user_friendly_name varchar(254) not null, - deprecated tinyint not null default 0, + id integer not null auto_increment, + create_time integer not null, + name varchar(254) not null, + user_friendly_name varchar(254) not null, + deprecated tinyint not null default 0, primary key (id) ) engine=InnoDB; @@ -56,153 +56,153 @@ create table app ( ) engine=InnoDB; create table app_version ( - id integer not null auto_increment, - create_time integer not null, - appid integer not null, - version_num integer not null, - platformid integer not null, - xml_doc mediumblob, - min_core_version integer not null default 0, - max_core_version integer not null default 0, - deprecated tinyint not null default 0, - plan_class varchar(254) not null default '', - pfc_n double not null default 0, - pfc_avg double not null default 0, - pfc_scale double not null default 0, - expavg_credit double not null default 0, - expavg_time double not null default 0, + id integer not null auto_increment, + create_time integer not null, + appid integer not null, + version_num integer not null, + platformid integer not null, + xml_doc mediumblob, + min_core_version integer not null default 0, + max_core_version integer not null default 0, + deprecated tinyint not null default 0, + plan_class varchar(254) not null default '', + pfc_n double not null default 0, + pfc_avg double not null default 0, + pfc_scale double not null default 0, + expavg_credit double not null default 0, + expavg_time double not null default 0, primary key (id) ) engine=InnoDB; create table user ( - id integer not null auto_increment, - create_time integer not null, - email_addr varchar(254) not null, - name varchar(254), - authenticator varchar(254), - country varchar(254), - postal_code varchar(254), - total_credit double not null, - expavg_credit double not null, - expavg_time double not null, - global_prefs blob, - project_prefs blob, - teamid integer not null, - venue varchar(254) not null, - url varchar(254), - send_email smallint not null, - show_hosts smallint not null, - posts smallint not null, + id integer not null auto_increment, + create_time integer not null, + email_addr varchar(254) not null, + name varchar(254), + authenticator varchar(254), + country varchar(254), + postal_code varchar(254), + total_credit double not null, + expavg_credit double not null, + expavg_time double not null, + global_prefs blob, + project_prefs blob, + teamid integer not null, + venue varchar(254) not null, + url varchar(254), + send_email smallint not null, + show_hosts smallint not null, + posts smallint not null, -- reused: salt for weak auth - seti_id integer not null, - seti_nresults integer not null, - seti_last_result_time integer not null, - seti_total_cpu double not null, - signature varchar(254), + seti_id integer not null, + seti_nresults integer not null, + seti_last_result_time integer not null, + seti_total_cpu double not null, + signature varchar(254), -- deprecated - has_profile smallint not null, - cross_project_id varchar(254) not null, - passwd_hash varchar(254) not null, - email_validated smallint not null, - donated smallint not null, + has_profile smallint not null, + cross_project_id varchar(254) not null, + passwd_hash varchar(254) not null, + email_validated smallint not null, + donated smallint not null, primary key (id) ) engine=InnoDB; create table team ( - id integer not null auto_increment, - create_time integer not null, - userid integer not null, - name varchar(254) not null, - name_lc varchar(254), - url varchar(254), - type integer not null, - name_html varchar(254), - description text, - nusers integer not null, /* temp */ - country varchar(254), - total_credit double not null, /* temp */ - expavg_credit double not null, /* temp */ - expavg_time double not null, - seti_id integer not null, - ping_user integer not null default 0, - ping_time integer unsigned not null default 0, - joinable tinyint not null default 1, + id integer not null auto_increment, + create_time integer not null, + userid integer not null, + name varchar(254) not null, + name_lc varchar(254), + url varchar(254), + type integer not null, + name_html varchar(254), + description text, + nusers integer not null, /* temp */ + country varchar(254), + total_credit double not null, /* temp */ + expavg_credit double not null, /* temp */ + expavg_time double not null, + seti_id integer not null, + ping_user integer not null default 0, + ping_time integer unsigned not null default 0, + joinable tinyint not null default 1, primary key (id) ) engine=MyISAM; create table host ( - id integer not null auto_increment, - create_time integer not null, - userid integer not null, - rpc_seqno integer not null, - rpc_time integer not null, - total_credit double not null, - expavg_credit double not null, - expavg_time double not null, + id integer not null auto_increment, + create_time integer not null, + userid integer not null, + rpc_seqno integer not null, + rpc_time integer not null, + total_credit double not null, + expavg_credit double not null, + expavg_time double not null, - timezone integer not null, - domain_name varchar(254), - serialnum varchar(254), - last_ip_addr varchar(254), - nsame_ip_addr integer not null, + timezone integer not null, + domain_name varchar(254), + serialnum varchar(254), + last_ip_addr varchar(254), + nsame_ip_addr integer not null, - on_frac double not null, - connected_frac double not null, - active_frac double not null, - cpu_efficiency double not null, - duration_correction_factor double not null, - p_ncpus integer not null, - p_vendor varchar(254), - p_model varchar(254), - p_fpops double not null, - p_iops double not null, - p_membw double not null, + on_frac double not null, + connected_frac double not null, + active_frac double not null, + cpu_efficiency double not null, + duration_correction_factor double not null, + p_ncpus integer not null, + p_vendor varchar(254), + p_model varchar(254), + p_fpops double not null, + p_iops double not null, + p_membw double not null, - os_name varchar(254), - os_version varchar(254), + os_name varchar(254), + os_version varchar(254), - m_nbytes double not null, - m_cache double not null, - m_swap double not null, + m_nbytes double not null, + m_cache double not null, + m_swap double not null, - d_total double not null, - d_free double not null, - d_boinc_used_total double not null, - d_boinc_used_project double not null, - d_boinc_max double not null, + d_total double not null, + d_free double not null, + d_boinc_used_total double not null, + d_boinc_used_project double not null, + d_boinc_max double not null, - n_bwup double not null, - n_bwdown double not null, + n_bwup double not null, + n_bwdown double not null, - credit_per_cpu_sec double not null, - venue varchar(254) not null, - nresults_today integer not null, - avg_turnaround double not null, - host_cpid varchar(254), - external_ip_addr varchar(254), - max_results_day integer not null, - error_rate double not null default 0, + credit_per_cpu_sec double not null, + venue varchar(254) not null, + nresults_today integer not null, + avg_turnaround double not null, + host_cpid varchar(254), + external_ip_addr varchar(254), + max_results_day integer not null, + error_rate double not null default 0, primary key (id) ) engine=InnoDB; -- see comments in boinc_db.h create table host_app_version ( - host_id integer not null, - app_version_id integer not null, - pfc_n double not null, - pfc_avg double not null, - et_n double not null, - et_avg double not null, - et_var double not null, - et_q double not null, - max_jobs_per_day integer not null, - n_jobs_today integer not null, - turnaround_n double not null, - turnaround_avg double not null, - turnaround_var double not null, - turnaround_q double not null, - consecutive_valid integer not null + host_id integer not null, + app_version_id integer not null, + pfc_n double not null, + pfc_avg double not null, + et_n double not null, + et_avg double not null, + et_var double not null, + et_q double not null, + max_jobs_per_day integer not null, + n_jobs_today integer not null, + turnaround_n double not null, + turnaround_avg double not null, + turnaround_var double not null, + turnaround_q double not null, + consecutive_valid integer not null ) engine = InnoDB; /* @@ -212,166 +212,176 @@ create table host_app_version ( * is stored in the XML doc */ create table workunit ( - id integer not null auto_increment, - create_time integer not null, - appid integer not null, - name varchar(254) not null, - xml_doc blob, - batch integer not null, - rsc_fpops_est double not null, - rsc_fpops_bound double not null, - rsc_memory_bound double not null, - rsc_disk_bound double not null, - need_validate smallint not null, - canonical_resultid integer not null, - canonical_credit double not null, - transition_time integer not null, - delay_bound integer not null, - error_mask integer not null, - file_delete_state integer not null, - assimilate_state integer not null, - hr_class integer not null, - opaque double not null, - min_quorum integer not null, - target_nresults integer not null, - max_error_results integer not null, - max_total_results integer not null, - max_success_results integer not null, - result_template_file varchar(63) not null, - priority integer not null, - mod_time timestamp, - rsc_bandwidth_bound double not null, - fileset_id integer not null, - app_version_id integer not null, + id integer not null auto_increment, + create_time integer not null, + appid integer not null, + name varchar(254) not null, + xml_doc blob, + batch integer not null, + rsc_fpops_est double not null, + rsc_fpops_bound double not null, + rsc_memory_bound double not null, + rsc_disk_bound double not null, + need_validate smallint not null, + canonical_resultid integer not null, + canonical_credit double not null, + transition_time integer not null, + delay_bound integer not null, + error_mask integer not null, + file_delete_state integer not null, + assimilate_state integer not null, + hr_class integer not null, + opaque double not null, + min_quorum integer not null, + target_nresults integer not null, + max_error_results integer not null, + max_total_results integer not null, + max_success_results integer not null, + result_template_file varchar(63) not null, + priority integer not null, + mod_time timestamp, + rsc_bandwidth_bound double not null, + fileset_id integer not null, + app_version_id integer not null, primary key (id) ) engine=InnoDB; create table result ( - id integer not null auto_increment, - create_time integer not null, - workunitid integer not null, - server_state integer not null, - outcome integer not null, - client_state integer not null, - hostid integer not null, - userid integer not null, - report_deadline integer not null, - sent_time integer not null, - received_time integer not null, - name varchar(254) not null, - cpu_time double not null, - xml_doc_in blob, - xml_doc_out blob, - stderr_out blob, - batch integer not null, - file_delete_state integer not null, - validate_state integer not null, - claimed_credit double not null, - granted_credit double not null, - opaque double not null, - random integer not null, - app_version_num integer not null, - appid integer not null, - exit_status integer not null, - teamid integer not null, - priority integer not null, - mod_time timestamp, - elapsed_time double not null, - flops_estimate double not null, - app_version_id integer not null, + id integer not null auto_increment, + create_time integer not null, + workunitid integer not null, + server_state integer not null, + outcome integer not null, + client_state integer not null, + hostid integer not null, + userid integer not null, + report_deadline integer not null, + sent_time integer not null, + received_time integer not null, + name varchar(254) not null, + cpu_time double not null, + xml_doc_in blob, + xml_doc_out blob, + stderr_out blob, + batch integer not null, + file_delete_state integer not null, + validate_state integer not null, + claimed_credit double not null, + granted_credit double not null, + opaque double not null, + random integer not null, + app_version_num integer not null, + appid integer not null, + exit_status integer not null, + teamid integer not null, + priority integer not null, + mod_time timestamp, + elapsed_time double not null, + flops_estimate double not null, + app_version_id integer not null, + runtime_outlier tinyint not null, primary key (id) ) engine=InnoDB; -- see boinc_db.h for doc create table batch ( - id serial primary key, - user_id integer not null, - create_time integer not null, - logical_start_time double not null, - logical_end_time double not null, - est_completion_time double not null, - njobs integer not null, - fraction_done double not null, - nerror_jobs integer not null, - state integer not null, - completion_time double not null, - credit_estimate double not null, - credit_canonical double not null, - credit_total double not null, - name varchar(255) not null, - app_id integer not null + id serial primary key, + user_id integer not null, + create_time integer not null, + logical_start_time double not null, + logical_end_time double not null, + est_completion_time double not null, + njobs integer not null, + fraction_done double not null, + nerror_jobs integer not null, + state integer not null, + completion_time double not null, + credit_estimate double not null, + credit_canonical double not null, + credit_total double not null, + name varchar(255) not null, + app_id integer not null ) engine = InnoDB; -- permissions for job submission -- create table user_submit ( - user_id integer not null, - quota double not null, - logical_start_time double not null, - all_apps tinyint not null, - create_apps tinyint not null, - create_app_versions tinyint not null + user_id integer not null, + quota double not null, + logical_start_time double not null, + submit_all tinyint not null, + -- can submit jobs to any app + manage_all tinyint not null, + -- manager privileges for all apps + -- grant/revoke permissions (except manage), change quotas + -- create apps ) engine = InnoDB; -- (user, app) submit permissions +-- The existence of the record implies permission to submit jobs -- create table user_submit_app ( - user_id integer not null, - app_id integer not null + user_id integer not null, + app_id integer not null + manage tinyint not null + -- can + -- create/deprecated app versions of this app + -- grant/revoke permissions (except admin) this app + -- abort their jobs ) engine = InnoDB; -- the following are used to implement trickle messages create table msg_from_host ( - id integer not null auto_increment, - create_time integer not null, - hostid integer not null, - variety varchar(254) not null, - handled smallint not null, - xml mediumtext, + id integer not null auto_increment, + create_time integer not null, + hostid integer not null, + variety varchar(254) not null, + handled smallint not null, + xml mediumtext, primary key (id) ) engine=InnoDB; create table msg_to_host ( - id integer not null auto_increment, - create_time integer not null, - hostid integer not null, - variety varchar(254) not null, - handled smallint not null, - xml mediumtext, + id integer not null auto_increment, + create_time integer not null, + hostid integer not null, + variety varchar(254) not null, + handled smallint not null, + xml mediumtext, primary key (id) ) engine=InnoDB; -- An assignment of a WU to a specific host, user, or team, or to all hosts -- create table assignment ( - id integer not null auto_increment, - create_time integer not null, - target_id integer not null, + id integer not null auto_increment, + create_time integer not null, + target_id integer not null, -- ID of target entity (see below) - target_type integer not null, + target_type integer not null, -- 0=none, 1=host, 2=user, 3=team - multi tinyint not null, + multi tinyint not null, -- 0=single host, 1=all hosts in set - workunitid integer not null, - resultid integer not null, + workunitid integer not null, + resultid integer not null, -- if not multi, the result primary key (id) ) engine = InnoDB; -- the following not used for anything right now create table state_counts ( - appid integer not null, - last_update_time integer not null, - result_server_state_2 integer not null, - result_server_state_4 integer not null, - result_file_delete_state_1 integer not null, - result_file_delete_state_2 integer not null, + appid integer not null, + last_update_time integer not null, + result_server_state_2 integer not null, + result_server_state_4 integer not null, + result_file_delete_state_1 integer not null, + result_file_delete_state_2 integer not null, result_server_state_5_and_file_delete_state_0 integer not null, workunit_need_validate_1 integer not null, workunit_assimilate_state_1 integer not null, - workunit_file_delete_state_1 integer not null, - workunit_file_delete_state_2 integer not null, + workunit_file_delete_state_1 integer not null, + workunit_file_delete_state_2 integer not null, primary key (appid) ) engine=MyISAM; @@ -382,16 +392,16 @@ create table state_counts ( -- user profile (description, pictures) -- create table profile ( - userid integer not null, - language varchar(254), - response1 text, - response2 text, - has_picture smallint not null, - recommend integer not null, - reject integer not null, - posts integer not null, - uotd_time integer, - verification integer not null, + userid integer not null, + language varchar(254), + response1 text, + response2 text, + has_picture smallint not null, + recommend integer not null, + reject integer not null, + posts integer not null, + uotd_time integer, + verification integer not null, -- UOD screening status: -1 denied, 0 unrated, 1 approved primary key (userid) ) engine=MyISAM; diff --git a/html/inc/result.inc b/html/inc/result.inc index e8313205ac..995dd8570a 100644 --- a/html/inc/result.inc +++ b/html/inc/result.inc @@ -19,6 +19,8 @@ require_once("../inc/translation.inc"); require_once("../inc/dir_hier.inc"); +// used by app_version_string(), see below +// $apps = array(); $app_versions = array(); @@ -539,39 +541,6 @@ function result_navigation($info, $where_clause) { return $x; } -function get_outfile_names($result) { - $names = array(); - $xml = "".$result->xml_doc_out.""; - $r = simplexml_load_string($xml); - if (!$r) return $names; - foreach ($r->file_info as $fi) { - $names[] = (string)($fi->name); - } - return $names; -} - -function get_outfile_paths($result) { - $fanout = parse_config(get_config(), ""); - $upload_dir = parse_config(get_config(), ""); - - $paths = array(); - $xml = "".$result->xml_doc_out.""; - $r = simplexml_load_string($xml); - if (!$r) return $paths; - foreach ($r->file_info as $fi) { - $path = dir_hier_path((string)($fi->name), $upload_dir, $fanout); - $paths[] = $path; - } - return $paths; -} - -function abort_workunit($wu) { - BoincResult::update_aux( - "server_state=5, outcome=5 where server_state=2 and workunitid=$wu->id" - ); - $wu->update("error_mask=error_mask|16"); -} - $cvs_version_tracker[]="\$Id$"; //Generated automatically - do not edit ?> diff --git a/html/inc/submit.inc b/html/inc/submit.inc index d171102811..e3ebe390cc 100644 --- a/html/inc/submit.inc +++ b/html/inc/submit.inc @@ -106,17 +106,6 @@ function batch_xml_to_object($batch) { return $b; } -function batch_state_string($state) { - switch ($state) { - case BATCH_STATE_INIT: return "new"; - case BATCH_STATE_IN_PROGRESS: return "in progress"; - case BATCH_STATE_COMPLETE: return "completed"; - case BATCH_STATE_ABORTED: return "aborted"; - case BATCH_STATE_RETIRED: return "retired"; - } - return "unknown state $state"; -} - //// API functions follow function boinc_estimate_batch($req) { diff --git a/html/inc/submit_util.inc b/html/inc/submit_util.inc new file mode 100644 index 0000000000..d8b8783f9b --- /dev/null +++ b/html/inc/submit_util.inc @@ -0,0 +1,86 @@ +. + +// utility functions for remote job submissions and control + +require_once("../inc/submit_db.inc"); + +function get_outfile_names($result) { + $names = array(); + $xml = "".$result->xml_doc_out.""; + $r = simplexml_load_string($xml); + if (!$r) return $names; + foreach ($r->file_info as $fi) { + $names[] = (string)($fi->name); + } + return $names; +} + +function get_outfile_paths($result) { + $fanout = parse_config(get_config(), ""); + $upload_dir = parse_config(get_config(), ""); + + $paths = array(); + $xml = "".$result->xml_doc_out.""; + $r = simplexml_load_string($xml); + if (!$r) return $paths; + foreach ($r->file_info as $fi) { + $path = dir_hier_path((string)($fi->name), $upload_dir, $fanout); + $paths[] = $path; + } + return $paths; +} + +function abort_workunit($wu) { + BoincResult::update_aux( + "server_state=5, outcome=5 where server_state=2 and workunitid=$wu->id" + ); + $wu->update("error_mask=error_mask|16"); +} + +function abort_batch($batch) { + $wus = BoincWorkunit::enum("batch=$batch->id"); + foreach ($wus as $wu) { + abort_workunit($wu); + } + $batch->update("state=".BATCH_STATE_ABORTED); + return 0; +} + +function retire_batch($batch) { + $wus = BoincWorkunit::enum("batch=$batch_id"); + $now = time(); + foreach ($wus as $wu) { + $wu->update("assimilate_state=2, transition_time=$now"); + } + $batch->update("state=".BATCH_STATE_RETIRED); +} + +function batch_state_string($state) { + switch ($state) { + case BATCH_STATE_INIT: return "new"; + case BATCH_STATE_IN_PROGRESS: return "in progress"; + case BATCH_STATE_COMPLETE: return "completed"; + case BATCH_STATE_ABORTED: return "aborted"; + case BATCH_STATE_RETIRED: return "retired"; + } + return "unknown state $state"; +} + +?> diff --git a/html/ops/db_update.php b/html/ops/db_update.php index 9e3f73d4e2..22c77880c1 100755 --- a/html/ops/db_update.php +++ b/html/ops/db_update.php @@ -781,6 +781,21 @@ function update_9_15_2011() { "); } +function update_9_20_2011() { + do_query(" + alter table user_submit + drop column all_apps, + drop column create_apps, + drop column create_app_versions, + add submit_all tinyint not null, + add manage_all tinyint not null + "); + + do_query(" + alter table user_submit_app + add manage tinyint not null + "); +} // Updates are done automatically if you use "upgrade". // // If you need to do updates manually, @@ -803,6 +818,9 @@ $db_updates = array ( array(23881, "update_7_26_2011"), array(24137, "update_9_6_2011"), array(24225, "update_9_15_2011"), + array(24248, "update_9_20_2011"), ); + + ?> diff --git a/html/user/manage.php b/html/user/manage.php new file mode 100644 index 0000000000..e52da6badb --- /dev/null +++ b/html/user/manage.php @@ -0,0 +1,70 @@ +. + +// top-level management page; +// shows links to the various functions available to the user. +// If the only option is managing a particular app, +// redirect to that page + +require_once("../inc/submit_db.inc"); +require_once("../inc/util.inc"); + +$user = get_logged_in_user(); + +$bus = BoincUserSubmit::lookup_userid($user->id); +if (!$bus) die("no access"); + +if ($bus->manage_all) { + page_head("Management functions"); + echo " + Project-wide management + "; + $apps = BoincApp::enum(null); + echo " +

Application-specific management: +

    + "; + foreach ($apps as $app) { + echo " +
  • id>$app->name + "; + } + echo "
\n"; + page_tail(); + exit; +} + +$apps = BoincUserSubmit::enum("user_id=$user->id and manage<>1"); +switch (count($apps)) { +case 0: + error_page("Nothing to manage"); +case 1: + $app = $apps[0]; + Header("Location: manage_app.php?app_id=$app->id"); + exit; +default: + page_head("Management functions"); + foreach ($apps as $app) { + echo " +

id>Manage $app->name + "; + } + page_tail(); +} + +?> diff --git a/html/user/manage_app.php b/html/user/manage_app.php new file mode 100644 index 0000000000..715f3673bb --- /dev/null +++ b/html/user/manage_app.php @@ -0,0 +1,231 @@ +. + +// app-specific management interface + +error_reporting(E_ALL); +ini_set('display_errors', true); +ini_set('display_startup_errors', true); + +require_once("../inc/submit_util.inc"); +require_once("../inc/util.inc"); + +function main_page($app) { + page_head("Management functions for $app->name"); + echo " + id&action=app_version_form>Manage app versions +

+ id&action=permissions_form>Manage user permissions +

+ id&action=batches_form>Manage jobs + "; + page_tail(); +} + +function app_version_form($app) { + page_head("Manage app versions"); + echo " +

+ + id> + "; + $avs = BoincAppVersion::enum("appid=$app->id"); + start_table(); + table_header("platform", "plan class", "version#", "deprecated"); + foreach ($avs as $av) { + $platform = BoincPlatform::lookup_id($av->platformid); + $c = $av->deprecated?"checked":""; + echo " + + $platform->name + $av->plan_class + $av->version_num + id $c> + + "; + } + echo " + +
+
+
+ + + "; + end_table(); + echo "\n"; + page_tail(); +} + +function app_version_action($app) { + $avs = BoincAppVersion::enum("appid=$app->id"); + foreach ($avs as $av) { + $x = get_str("dep_$av->id", true); + if ($x) { + if (!$av->deprecated) { + $av->update("deprecated=1"); + } + } else { + if ($av->deprecated) { + $av->update("deprecated=0"); + } + } + } + page_head("Update successful"); + echo " + id>Return to application management page + "; + page_tail(); +} + +function permissions_form($app) { + page_head("Manage user permissions for $app->name"); + echo " + + + id> + "; + $busas = BoincUserSubmitApp::enum("app_id=$app->id"); + start_table(); + table_header("User", "Allowed to submit jobs to $app->name"); + foreach ($busas as $busa) { + $user = BoincUser::lookup_id($busa->user_id); + echo " + + $user->name ($user->id) + id checked> + + "; + } + echo " + + Add new user + User ID: + + +
+ + + "; + end_table(); + echo "\n"; + page_tail(); +} + +function permissions_action($app) { + $busas = BoincUserSubmitApp::enum("app_id=$app->id"); + foreach ($busas as $busa) { + if (!get_str("user_$busa->user_id", true)) { + BoincUserSubmitApp::delete_user($busa->user_id); + } + } + $userid = get_int("new_user_id", true); + if ($userid) { + BoincUserSubmitApp::insert("(user_id, app_id) values ($userid, $app->id)"); + } + page_head("Update successful"); + echo " + id>Return to application management page + "; + page_tail(); +} + +function batches_form($app) { + page_head("Manage jobs for $app->name"); + echo " + + + id> + "; + start_table(); + table_header("Batch ID", "Submitter", "Submitted", "State", "# jobs", "Abort?"); + $batches = BoincBatch::enum("app_id=$app->id"); + foreach ($batches as $batch) { + $user = BoincUser::lookup_id($batch->user_id); + echo " + $batch->id + $user->name + ".time_str($batch->create_time)." + ".batch_state_string($batch->state)." + $batch->njobs + id> + + "; + } + echo " + Abort all jobs for $app->name? + + + "; + echo " +
+
+
+ + + "; + end_table(); + page_tail(); +} + +function batches_action($app) { + $batches = BoincBatch::enum("app_id=$app->id"); + $abort_all = (get_str("abort_all", true)); + foreach ($batches as $batch) { + if ($abort_all || get_str("abort_$batch->id", true)) { + abort_batch($batch); + } + } + page_head("Update successful"); + echo " + id>Return to application management page + "; + page_tail(); +} + +$user = get_logged_in_user(); +$app_id = get_int("app_id"); +$app = BoincApp::lookup_id($app_id); +if (!$app) error_page("no such app"); +$bus = BoincUserSubmit::lookup_userid($user->id); +if (!$bus) error_page("no access"); +if (!$bus->manage_all) { + $busa = BoincUserSubmitApp::lookup("user_id=$user->id and app_id=$app_id"); + if (!$busa || !$busa->manage) error_page("no access"); +} + +$action = get_str("action", true); +switch ($action) { +case "": + main_page($app); break; +case "app_version_form": + app_version_form($app); break; +case "app_version_action": + app_version_action($app); break; +case "permissions_form": + permissions_form($app); break; +case "permissions_action": + permissions_action($app); break; +case "batches_form": + batches_form($app); break; +case "batches_action": + batches_action($app); break; +default: + error_page("unknown action $action"); +} +?> diff --git a/html/ops/submit_permissions.php b/html/user/manage_project.php similarity index 66% rename from html/ops/submit_permissions.php rename to html/user/manage_project.php index bbf7c33088..273a3e3d40 100644 --- a/html/ops/submit_permissions.php +++ b/html/user/manage_project.php @@ -17,21 +17,24 @@ // You should have received a copy of the GNU Lesser General Public License // along with BOINC. If not, see . -// Interface for controlling user permissions to submit jobs +// Interface for project-wide functions: +// - control user quotas and permissions to submit jobs +// - create apps require_once("../inc/submit_db.inc"); require_once("../inc/util.inc"); -require_once("../inc/util_ops.inc"); function user_row($u) { $user = BoincUser::lookup_id($u->user_id); echo " - $user->name
$user->email_addr + $user->name (ID: $user->id) + user_id>Edit permissions + "; echo ""; - if ($u->all_apps) { - echo "All\n"; + if ($u->submit_all) { + echo "All applications\n"; } else { $uas = BoincUserSubmitApp::enum("user_id=$u->user_id"); foreach ($uas as $ua) { @@ -42,46 +45,53 @@ function user_row($u) { echo "\n"; echo "$u->quota\n"; echo " - user_id>Edit "; } function handle_list() { - admin_page_head("User job submission permissions"); + page_head("Project-wide management functions"); + echo "

User permissions and quotas

+ The following users are allowed to remotely submit jobs. +

+ "; + show_button("manage_project.php?action=add_form", + "Add user", "Allow a new user to submit jobs" + ); + $us = BoincUserSubmit::enum(""); start_table(); - table_header("User", "Applications", "Quota", ""); + table_header("User", "Can submit jobs for", "Quota"); foreach ($us as $u) { user_row($u); } end_table(); - show_button("submit_permissions.php?action=add_form", "Add user", "Add user"); - admin_page_tail(); + page_tail(); } function handle_edit_form() { $user_id = get_int('user_id'); $user = BoincUser::lookup_id($user_id); $usub = BoincUserSubmit::lookup_userid($user_id); - admin_page_head("Set permissions for $user->name"); + page_head("Permissions for $user->name"); echo " - Can submit jobs for: - + $user->name is allowed to submit jobs for: +

+ "; - if ($usub->all_apps) { + if ($usub->submit_all) { $all_checked = "checked"; $not_all_checked = ""; } else { $all_checked = ""; $not_all_checked = "checked"; } - echo " All apps + echo " All apps
- Only selected apps: + Only selected apps: "; $apps = BoincApp::enum("deprecated=0"); foreach ($apps as $app) { @@ -93,29 +103,28 @@ function handle_edit_form() { $sav = $usub->create_app_versions?"checked":""; $sa = $usub->create_apps?"checked":""; echo " -

- Can create new versions of the above apps -

- Can create new apps

Quota: + This determines how much computing capacity is allocated to $user->name.

+

+ Return to project-wide management functions "; - admin_page_tail(); + page_tail(); } function handle_edit_action() { $user_id = get_int('user_id'); $us = BoincUserSubmit::lookup_userid($user_id); - if (!$us) admin_error_page("user not found"); + if (!$us) error_page("user not found"); BoincUserSubmitApp::delete_user($user_id); - $all_apps = get_str('all_apps'); - if ($all_apps) { - $us->update("all_apps=1"); + $submit_all = get_str('submit_all'); + if ($submit_all) { + $us->update("submit_all=1"); } else { - $us->update("all_apps=0"); + $us->update("submit_all=0"); $apps = BoincApp::enum("deprecated=0"); foreach ($apps as $app) { $x = "app_$app->id"; @@ -128,39 +137,41 @@ function handle_edit_action() { if ($quota != $us->quota) { $us->update("quota=$quota"); } - $x = get_str('create_apps', true)?1:0; - $us->update("create_apps=$x"); - $x = get_str('create_app_versions', true)?1:0; - $us->update("create_app_versions=$x"); - - admin_page_head("User permissions updated"); - admin_page_tail(); + page_head("Update successful"); + echo "Return to project-wide management functions"; + page_tail(); } function handle_add_form() { - admin_page_head("Add user"); + page_head("Add user"); echo " -

+ User ID:
"; - admin_page_tail(); + page_tail(); } function handle_add_action() { $user_id = get_int('user_id'); $user = BoincUser::lookup_id($user_id); - if (!$user) admin_error_page("no such user"); + if (!$user) error_page("no such user"); $us = BoincUserSubmit::lookup_userid($user_id); if (!$us) { if (!BoincUserSubmit::insert("(user_id) values ($user_id)")) { - admin_error_page("Insert failed"); + error_page("Insert failed"); } } - header("Location: submit_permissions.php?action=edit_form&user_id=$user_id"); + header("Location: manage_project.php?action=edit_form&user_id=$user_id"); +} + +$user = get_logged_in_user(); +$bus = BoincUserSubmit::lookup_userid($user->id); +if (!$bus) { + die("no access"); } $action = get_str('action', true); @@ -177,7 +188,7 @@ case 'edit_form': case 'edit_action': handle_edit_action(); break; default: - admin_error_page("unknown action: $action"); + error_page("unknown action: $action"); } ?> diff --git a/html/user/submit.php b/html/user/submit.php index 6f7808bafa..6d6af80c36 100644 --- a/html/user/submit.php +++ b/html/user/submit.php @@ -24,6 +24,7 @@ require_once("../inc/submit_db.inc"); require_once("../inc/xml.inc"); require_once("../inc/dir_hier.inc"); require_once("../inc/result.inc"); +require_once("../inc/submit_util.inc"); error_reporting(E_ALL); ini_set('display_errors', true); @@ -41,7 +42,7 @@ function authenticate_user($r, $app) { if (!$user) error("bad authenticator"); $user_submit = BoincUserSubmit::lookup_userid($user->id); if (!$user_submit) error("no submit access"); - if ($app && !$user_submit->all_apps) { + if ($app && !$user_submit->submit_all) { $usa = BoincUserSubmitApp::lookup("user_id=$user->id and app_id=$app->id"); if (!$usa) { error("no submit access"); @@ -326,34 +327,25 @@ function query_job($r) { echo "\n"; } -function abort_batch($r) { +function handle_abort_batch($r) { list($user, $user_submit) = authenticate_user($r, null); $batch_id = (int)($r->batch_id); $batch = BoincBatch::lookup_id($batch_id); if ($batch->user_id != $user->id) { error("not owner"); } - $wus = BoincWorkunit::enum("batch=$batch_id"); - foreach ($wus as $wu) { - abort_workunit($wu); - } - $batch->update("state=".BATCH_STATE_ABORTED); + abort_batch($batch); echo "1"; } -function retire_batch($r) { +function handle_retire_batch($r) { list($user, $user_submit) = authenticate_user($r, null); $batch_id = (int)($r->batch_id); $batch = BoincBatch::lookup_id($batch_id); if ($batch->user_id != $user->id) { error("not owner"); } - $wus = BoincWorkunit::enum("batch=$batch_id"); - $now = time(); - foreach ($wus as $wu) { - $wu->update("assimilate_state=2, transition_time=$now"); - } - $batch->update("state=".BATCH_STATE_RETIRED); + retire_batch($batch); echo "1"; } @@ -413,8 +405,8 @@ switch ($r->getName()) { case 'query_batches': query_batches($r); break; case 'query_batch': query_batch($r); break; case 'query_job': query_job($r); break; - case 'abort_batch': abort_batch($r); break; - case 'retire_batch': retire_batch($r); break; + case 'abort_batch': handle_abort_batch($r); break; + case 'retire_batch': handle_retire_batch($r); break; default: error("bad command"); } diff --git a/html/user/submit_example.php b/html/user/submit_example.php index f9c62ff9aa..39749e7586 100644 --- a/html/user/submit_example.php +++ b/html/user/submit_example.php @@ -149,7 +149,7 @@ function eligible_apps() { if (!$user_submit) return null; $a = array(); foreach($apps as $app) { - if ($user_submit->all_apps) { + if ($user_submit->submit_all) { $a[] = $app; } else { if (BoincUserSubmitApp::lookup("user_id=$user->id and app_id=$app->id")) { diff --git a/py/Boinc/setup_project.py b/py/Boinc/setup_project.py index 9170136c08..c85bc54004 100644 --- a/py/Boinc/setup_project.py +++ b/py/Boinc/setup_project.py @@ -384,6 +384,7 @@ sys.path.insert(0, os.path.join('%s', 'py')) [ 'appmgr', 'create_work', 'xadd', 'dbcheck_files_exist', 'run_in_ops', 'update_versions', 'parse_config', 'grep_logs', 'db_query', 'watch_tcp', 'sign_executable', 'dir_hier_move', + 'manage_privileges', 'dir_hier_path', 'boinc_submit', 'demo_submit', 'demo_query' ]) map(lambda (s): install(srcdir('lib',s), dir('bin',s)), [ 'crypt_prog' ]) diff --git a/tools/manage_privileges b/tools/manage_privileges new file mode 100755 index 0000000000..427b94adac --- /dev/null +++ b/tools/manage_privileges @@ -0,0 +1,162 @@ +#!/usr/bin/env php + +. + +// script for controlling "manage" privileges. +// See http://boinc.berkeley.edu/trac/wiki/MultiUser +// Run this from the project directory. + +// usage: +// +// manage_privileges grant userid {appname|all} +// manage_privileges revoke userid {appname|all} +// manage_privileges list +// +// grant/revoke the "manage" privileges for the given app (or all apps). + +error_reporting(E_ALL); +ini_set('display_errors', true); +ini_set('display_startup_errors', true); + +function usage() { + die("usage: +manage_privileges grant userid {appname|all} +manage_privileges revoke userid {appname|all} +manage_privileges list +"); +} + +function show_user($user) { + echo "$user->name (ID $user->id)\n"; +} + +function show_managers() { + echo "Users with global manager privileges: \n"; + $buss = BoincUserSubmit::enum("manage_all<>0"); + if (!count($buss)) { + echo "none\n"; + } else { + foreach ($buss as $bus) { + $user = BoincUser::lookup_id($bus->user_id); + show_user($user); + } + } + + $apps = BoincApp::enum(null); + foreach ($apps as $app) { + echo "\nUsers with manager privileges for $app->name:\n"; + $busas = BoincUserSubmitApp::enum("app_id=$app->id and manage<>0"); + if (!count($busas)) { + echo "none\n"; + } else { + foreach ($busas as $busa) { + $user = BoincUser::lookup_id($busa->user_id); + show_user($user); + } + } + } +} + +if (!file_exists("config.xml")) { + die("run this script from the project directory\n"); +} + +chdir("html/inc"); + +require_once("boinc_db.inc"); +require_once("submit_db.inc"); + +if ($argc < 2) usage(); + +if ($argv[1] == "list") { + show_managers(); + exit; +} + +if ($argc < 4) usage(); + +$user = BoincUser::lookup_id($argv[2]); +if (!$user) { + die("no such user: ".$argv[1]."\n"); +} + +if ($argv[1] === "grant") { + $grant = true; +} else if ($argv[1] === "revoke") { + $grant = false; +} else { + usage(); +} + +if ($argv[3] === "all") { + $bus = BoincUserSubmit::lookup_userid($user->id); + if ($bus) { + if ($grant) { + if ($bus->manage_all) { + die("User $user->id already has global manage access\n"); + } else { + $bus->update("manage_all=1"); + } + } else { + if ($bus->manage_all) { + $bus->update("manage_all=0"); + } else { + die("User $user->id does not have global manage access\n"); + } + } + } else { + if ($grant) { + BoincUserSubmit::insert("user_id=$user->id and manage_all=1"); + } else { + die("User $user->id does not have global manage access\n"); + } + } +} else { + $app = BoincApp::lookup_name($argv[3]); + if (!$app) die("no such app: ".$argv[2]."\n"); + $busa = BoincUserSubmitApp::lookup("user_id=$user->id and app_id=$app->id"); + if ($busa) { + if ($grant) { + if ($busa->manage) { + die("User $user->id already has manage access for $app->name\n"); + } else { + $busa->update("manage=1"); + } + } else { + if ($busa->manage) { + $busa->update("manage=0"); + } else { + die("User $user->id does not have manage access for $app->name\n"); + } + } + } else { + if ($grant) { + $bus = BoincUserSubmit::lookup(userid($user->id); + if (!$bus) { + BoincUserSubmit::insert("user_id=$user->id=1"); + } + BoincUserSubmitApp::insert("user_id=$user->id and app_id=$app->id and manage=1"); + } else { + die("User $user->id does not have manage access for $app->name\n"); + } + } +} +echo "Done.\n"; + +?>