Index: openacs-4/packages/acs-authentication/sql/oracle/acs-authentication-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/sql/oracle/acs-authentication-create.sql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/sql/oracle/acs-authentication-create.sql 8 Sep 2003 15:59:42 -0000 1.1 @@ -0,0 +1,8 @@ +-- +-- Data model for acs-authentication +-- +-- $Id: acs-authentication-create.sql,v 1.1 2003/09/08 15:59:42 lars Exp $ +-- + +@@ batch-job-tables-create.sql + Index: openacs-4/packages/acs-authentication/sql/oracle/acs-authentication-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/sql/oracle/acs-authentication-drop.sql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/sql/oracle/acs-authentication-drop.sql 8 Sep 2003 15:59:42 -0000 1.1 @@ -0,0 +1,8 @@ +-- +-- Drop script for acs-authentication +-- +-- $Id: acs-authentication-drop.sql,v 1.1 2003/09/08 15:59:42 lars Exp $ +-- + +@@ batch-job-tables-drop.sql + Index: openacs-4/packages/acs-authentication/sql/oracle/batch-job-tables-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/sql/oracle/batch-job-tables-create.sql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/sql/oracle/batch-job-tables-create.sql 8 Sep 2003 15:59:42 -0000 1.1 @@ -0,0 +1,71 @@ +create sequence auth_batch_jobs_job_id_seq; + +create table auth_batch_jobs ( + job_id integer + constraint auth_batch_jobs_pk + primary key, + job_start_time date default sysdate, + job_end_time date, + interactive_p char(1) + constraint auth_batch_jobs_interactive_ck + check (interactive_p in ('t', 'f')) + constraint auth_batch_jobs_interactive_nn + not null, + -- if interactive, by which user + creation_user integer + constraint auth_batch_job_user_fk + references users(user_id) + on delete set null, + -- status information for the GetDocument operation + doc_start_time date, + doc_end_time date, + doc_status varchar2(4000), + doc_message varchar2(4000), + document_id integer + constraint auth_batch_jobs_doc_fk + references cr_items(item_id) + on delete set null +); + +create index auth_batch_jobs_user_idx on auth_batch_jobs(creation_user); +create index auth_batch_jobs_document_idx on auth_batch_jobs(document_id); + + +create sequence auth_batch_job_entry_id_seq; + +create table auth_batch_job_entries ( + entry_id integer + constraint auth_batch_job_entries_pk + primary key, + job_id integer + constraint auth_batch_job_entries_job_fk + references auth_batch_jobs(job_id) + on delete cascade, + entry_time date default sysdate, + operation varchar(100) + constraint auth_batch_jobs_entries_op_ck + check (operation in ('insert', 'update', 'delete')), + authority_id integer + constraint auth_batch_job_entries_auth_fk + references auth_authorities(authority_id) + on delete cascade, + username varchar(100), + user_id integer + constraint auth_batch_job_entries_user_fk + references users(user_id) on delete set null, + success_p char(1) + constraint auth_batch_jobs_ent_success_ck + check (success_p in ('t', 'f')) + constraint auth_batch_jobs_ent_success_nn + not null, + message varchar2(4000), + element_messages clob +); + +create index auth_batch_job_ent_job_idx on auth_batch_job_entries(job_id); +create index auth_batch_job_ent_auth_idx on auth_batch_job_entries(authority_id); +create index auth_batch_job_ent_user_idx on auth_batch_job_entries(user_id); + + + + Index: openacs-4/packages/acs-authentication/sql/oracle/batch-job-tables-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/sql/oracle/batch-job-tables-drop.sql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/sql/oracle/batch-job-tables-drop.sql 8 Sep 2003 15:59:42 -0000 1.1 @@ -0,0 +1,6 @@ +drop sequence auth_batch_jobs_job_id_seq; +drop sequence auth_batch_job_entry_id_seq; + +drop table auth_batch_job_entries; + +drop table auth_batch_jobs; Index: openacs-4/packages/acs-authentication/sql/postgresql/acs-authentication-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/sql/postgresql/acs-authentication-create.sql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/sql/postgresql/acs-authentication-create.sql 8 Sep 2003 15:59:43 -0000 1.1 @@ -0,0 +1,8 @@ +-- +-- Data model for acs-authentication +-- +-- $Id: acs-authentication-create.sql,v 1.1 2003/09/08 15:59:43 lars Exp $ +-- + +\i batch-job-tables-create.sql + Index: openacs-4/packages/acs-authentication/sql/postgresql/acs-authentication-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/sql/postgresql/acs-authentication-drop.sql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/sql/postgresql/acs-authentication-drop.sql 8 Sep 2003 15:59:43 -0000 1.1 @@ -0,0 +1,8 @@ +-- +-- Drop script for acs-authentication +-- +-- $Id: acs-authentication-drop.sql,v 1.1 2003/09/08 15:59:43 lars Exp $ +-- + +\i batch-job-tables-drop.sql + Index: openacs-4/packages/acs-authentication/sql/postgresql/batch-job-tables-create.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/sql/postgresql/batch-job-tables-create.sql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/sql/postgresql/batch-job-tables-create.sql 8 Sep 2003 15:59:43 -0000 1.1 @@ -0,0 +1,66 @@ + +create sequence auth_batch_jobs_job_id_seq; + +create table auth_batch_jobs ( + job_id integer + constraint auth_batch_jobs_pk + primary key, + job_start_time timestamptz default current_timestamp, + job_end_time timestamptz, + interactive_p boolean + constraint auth_batch_jobs_interactive_nn + not null, + -- if interactive, by which user + creation_user integer + constraint auth_batch_job_user_fk + references users(user_id) + on delete set null, + -- status information for the GetDocument operation + doc_start_time timestamptz, + doc_end_time timestamptz, + doc_status text, + doc_message text, + document_id integer + constraint auth_batch_jobs_doc_fk + references cr_items(item_id) + on delete set null +); + +create index auth_batch_jobs_user_idx on auth_batch_jobs(creation_user); +create index auth_batch_jobs_document_idx on auth_batch_jobs(document_id); + + +create sequence auth_batch_job_entry_id_seq; + +create table auth_batch_job_entries ( + entry_id integer + constraint auth_batch_job_entries_pk + primary key, + job_id integer + constraint auth_batch_job_entries_job_fk + references auth_batch_jobs(job_id) + on delete cascade, + entry_time timestamptz default current_timestamp, + operation varchar(100) + constraint auth_batch_jobs_entries_op_ck + check (operation in ('insert', 'update', 'delete')), + authority_id integer + constraint auth_batch_job_entries_auth_fk + references auth_authorities(authority_id) + on delete cascade, + username varchar(100), + user_id integer + constraint auth_batch_job_entries_user_fk + references users(user_id) on delete set null, + success_p boolean not null, + message text, + element_messages text +); + +create index auth_batch_job_ent_job_idx on auth_batch_job_entries(job_id); +create index auth_batch_job_ent_auth_idx on auth_batch_job_entries(authority_id); +create index auth_batch_job_ent_user_idx on auth_batch_job_entries(user_id); + + + + Index: openacs-4/packages/acs-authentication/sql/postgresql/batch-job-tables-drop.sql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/sql/postgresql/batch-job-tables-drop.sql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/sql/postgresql/batch-job-tables-drop.sql 8 Sep 2003 15:59:43 -0000 1.1 @@ -0,0 +1,6 @@ +drop sequence auth_batch_jobs_job_id_seq; +drop sequence auth_batch_job_entry_id_seq; + +drop table auth_batch_job_entries; + +drop table auth_batch_jobs; Index: openacs-4/packages/acs-authentication/tcl/authentication-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/tcl/authentication-procs.tcl,v diff -u -N -r1.18 -r1.19 --- openacs-4/packages/acs-authentication/tcl/authentication-procs.tcl 4 Sep 2003 16:36:17 -0000 1.18 +++ openacs-4/packages/acs-authentication/tcl/authentication-procs.tcl 8 Sep 2003 15:59:43 -0000 1.19 @@ -608,7 +608,7 @@ {-secret_question ""} {-secret_answer ""} {-member_state "approved"} - {-email_verified_p "t"} + {-email_verified_p ""} } { Create the local account for a user. @@ -640,39 +640,41 @@ account_status ok account_message {} } - array set elm_msgs [list] - # TODO: This needs to be controlled by a parameter, to be added latter + # PHASE II: This needs to be controlled by a parameter if { [empty_string_p $username] } { set username $email } # Validate data - if { [string first "<" $first_names] != -1 } { - set element_messages(first_names) [_ acs-subsite.lt_You_cant_have_a_lt_in] + set user_vars { authority_id username first_names last_name email url secret_question secret_answer } + foreach varname $user_vars { + if { [info exists $varname] } { + set user_info($varname) [set $varname] + } } - if { [string first "<" $last_name] != -1 } { - set element_messages(last_name) [_ acs-subsite.lt_You_cant_have_a_lt_in_1] - } - - if { [empty_string_p $url] || [string equal $url "http://"] } { - # The user left the default hint for the url - set url {} - } elseif { ![util_url_valid_p $url] } { - set valid_url_example "http://openacs.org/" - set element_messages(url) [_ acs-subsite.lt_Your_URL_doesnt_have_] - } - if { ![empty_string_p [cc_lookup_email_user $email]] } { - set element_messages(email) "We already have a user with this email." - } - if { ![empty_string_p [acs_user::get_by_username -authority_id $authority_id -username $username]] } { - set element_messages(username) "We already have a user with this username." - } + auth::validate_user_info \ + -user_array user_info \ + -message_array element_messages + + # Handle validation errors if { [llength [array names element_messages]] > 0 } { - return [list creation_status data_error creation_message {} element_messages [array get element_messages]] + return [list \ + creation_status "data_error" \ + creation_message {} \ + element_messages [array get element_messages] \ + ] } + # Suck user info variables back out, they may have been modified by the validate helper proc + foreach varname $user_vars { + if { [info exists user_info($varname)] } { + set $varname $user_info($varname) + } + } + + # Admin approval if { [parameter::get -parameter RegistrationRequiresApprovalP -default 0] } { set member_state "needs approval" @@ -682,10 +684,12 @@ set member_state "approved" } - if { [parameter::get -parameter RegistrationRequiresEmailVerificationP -default 0] } { - set email_verified_p "f" - } else { - set email_verified_p "t" + if { [empty_string_p $email_verified_p] } { + if { [parameter::get -parameter RegistrationRequiresEmailVerificationP -default 0] } { + set email_verified_p "f" + } else { + set email_verified_p "t" + } } set error_p 0 @@ -712,10 +716,8 @@ if { $error_p || $user_id == 0 } { set result(creation_status) "failed_to_connect" set result(creation_message) "We experienced an error while trying to register an account for you." - if { $error_p } { - global errorInfo - ns_log Error "Error invoking account registratino driver for authority_id = $authority_id: $errorInfo" - } + global errorInfo + ns_log Error "Error creating local account.\n$errorInfo" return [array get result] } @@ -740,17 +742,285 @@ return [array get result] } + +ad_proc -public auth::update_local_account { + {-authority_id:required} + {-username:required} + {-first_names ""} + {-last_name ""} + {-email ""} + {-url ""} + {-secret_question ""} + {-secret_answer ""} + {-member_state "approved"} + {-email_verified_p ""} +} { + Update the local account for a user. + + @return Array list containing the following entries: + + + + All entries are guaranteed to always be set, but may be empty. +} { + array set result { + update_status update_error + update_message {} + element_messages {} + } + + # Validate data + + # Updating: Find the existing account + set user_vars { authority_id username first_names last_name email url secret_question secret_answer } + foreach varname $user_vars { + if { [info exists $varname] } { + set user_info($varname) [set $varname] + } + } + + auth::validate_user_info \ + -update \ + -user_array user_info \ + -message_array element_messages + + # Handle validation errors + if { [llength [array names element_messages]] > 0 } { + return [list \ + update_status "data_error" \ + update_message {} \ + element_messages [array get element_messages] \ + ] + } + + # Suck user info variables back out, they may have been modified by the validate helper proc + foreach varname $user_vars { + if { [info exists user_info($varname)] } { + set $varname $user_info($varname) + } + } + set user_id $user_info(user_id) + + set error_p 0 + with_catch errmsg { + + db_transaction { + # Update persons: first_names, last_name + person::update \ + -person_id $user_id \ + -first_names $first_names \ + -last_name $last_name + + # Update parties: email, url + if { [empty_string_p $email] } { + set success_p 0 + set message "Email is required" + } + party::update \ + -party_id $user_id \ + -email $email \ + -url $url + + # Update users: email_verified_p + if { ![empty_string_p $email_verified_p] } { + acs_user::update \ + -user_id $user_id \ + -email_verified_p $email_verified_p + } + + # TODO: Portrait + } + } { + set error_p 1 + } + + if { $error_p } { + set result(update_status) "failed_to_connect" + set result(update_message) "We experienced an error while trying to update the account information." + global errorInfo + ns_log Error "Error updating local account.\n$errorInfo" + return [array get result] + } + + # Update succeeded + set result(update_status) "ok" + + return [array get result] +} + + +ad_proc -public auth::delete_local_account { + {-authority_id:required} + {-username:required} +} { + Delete the local account for a user. + + @return Array list containing the following entries: + + + + All entries are guaranteed to always be set, but may be empty. +} { + array set result { + delete_status ok + delete_message {} + } + + set user_id [acs_user::get_by_username \ + -authority_id $authority_id \ + -username $username] + + if { [empty_string_p $user_id] } { + set result(delete_status) "delete_error" + set result(delete_messages) "No user found with this username" + return [array get result] + } + + # Mark the account banned + acs_user::ban -user_id $user_id + + return [array get result] +} + + +ad_proc -private auth::validate_user_info { + {-update:boolean} + {-user_array:required} + {-message_array:required} +} { + Validates user info and returns errors, if any. + + @param update Set this flag if you're updating an existing record, meaning we shouldn't check for duplicates. + + @param user_array Name of an array in the caller's namespace which contains the user info + (authority_id, username, email, first_names, last_name, url, secret_question, secret_answer). + + @param message_array Name of an array where you want the validation errors stored, keyed by element name. +} { + upvar 1 $user_array user + upvar 1 $message_array element_messages + + foreach elm { authority_id username first_names last_name email } { + if { ![exists_and_not_null user($elm)] } { + set element_messages(first_names) "Required" + } + } + + ns_log Notice "LARS: update_p = $update_p ; user(authority_id) = $user(authority_id) ; user(username) = $user(username)" + if { $update_p && [exists_and_not_null user(authority_id)] && [exists_and_not_null user(username)] } { + set user(user_id) [acs_user::get_by_username \ + -authority_id $user(authority_id) \ + -username $user(username)] + + ns_log Notice "LARS2: user(user_id) = $user(user_id)" + + if { [empty_string_p $user(user_id)] } { + set element_messages(username) "No user with username '$username' found for authority [auth::authority::get_element -authority_id $authority_id -element pretty_name]" + } + } + + if { [exists_and_not_null user(first_names)] && [string first "<" $user(first_names)] != -1 } { + set element_messages(first_names) [_ acs-subsite.lt_You_cant_have_a_lt_in] + } + + if { [exists_and_not_null user(last_name)] && [string first "<" $user(last_name)] != -1 } { + set element_messages(last_name) [_ acs-subsite.lt_You_cant_have_a_lt_in_1] + } + + if { [exists_and_not_null user(email)] && ![util_email_valid_p $user(email)] } { + set element_messages(email) "This is not a valid email address" + } else { + set user(email) [string tolower $user(email)] + } + + if { ![exists_and_not_null $user(url)] || ([info exists user(url)] && [string equal $user(url) "http://"]) } { + # The user left the default hint for the url + set user(url) {} + } elseif { ![util_url_valid_p $user(url)] } { + set valid_url_example "http://openacs.org/" + set element_messages(url) [_ acs-subsite.lt_Your_URL_doesnt_have_] + } + + if { [exists_and_not_null user(email)] } { + # Check that email is unique + set email $user(email) + ns_log Notice "LARS4: email = '$email' ; [db_list_of_lists count { select party_id, email from parties }]" + set email_party_id [party::get_by_email -email $user(email)] + + ns_log Notice "LARS3: email_party_id = $email_party_id" + if { ![empty_string_p $email_party_id] && (!$update_p || $email_party_id != $user(user_id)) } { + # We found a user with this email, and either we're not updating, or it's not the same user_id as the one we're updating + + if { ![string equal [acs_object_type $email_party_id] "user"] } { + set element_messages(email) "We already have a group with this email" + } else { + acs_user::get \ + -user_id $email_party_id \ + -array email_user + + switch $email_user(member_state) { + banned { + # A user with this email does exist, but he's banned, so we can 'steal' his email address + # by setting it to something dummy + party::update \ + -user_id $email_party_id \ + -email "dummy-email-$email_party_id" + } + default { + set element_messages(email) "We already have a user with this email." + } + } + } + } + } + + if { [exists_and_not_null user(username)] } { + # Check that username is unique + set username_user_id [acs_user::get_by_username -authority_id $user(authority_id) -username $user(username)] + + if { ![empty_string_p $username_user_id] && (!$update_p || $username_user_id != $user(user_id)) } { + # We found a user with this username, and either we're not updating, or it's not the same user_id as the one we're updating + + set username_member_state [acs_user::get_element -user_id $username_user_id -element member_state] + switch $username_member_state { + banned { + # A user with this username does exist, but he's banned, so we can 'steal' his username + # by setting it to something dummy + acs_user::update \ + -user_id $usrename_user_id \ + -username "dummy-username-$username_user_id" + } + default { + set element_messages(username) "We already have a user with this username." + } + } + } + } +} + ad_proc -public auth::set_email_verified { {-user_id:required} } { Update an OpenACS record with the fact that the email address on record was verified. } { - db_dml set_email_verified { - update users - set email_verified_p = 't' - where user_id = :user_id - } + acs_user::update \ + -user_id $user_id \ + -email_verified_p "t" } ##### Index: openacs-4/packages/acs-authentication/tcl/sync-procs-oracle.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/tcl/sync-procs-oracle.xql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/tcl/sync-procs-oracle.xql 8 Sep 2003 15:59:43 -0000 1.1 @@ -0,0 +1,66 @@ + + + + oracle8.1.6 + + + + select job_id, + job_start_time, + job_end_time, + interactive_p, + creation_user, + doc_start_time, + doc_end_time, + doc_status, + doc_message, + document_id, + (j.job_end_time - j.job_start_time) * 24*60*60 as run_time_seconds, + (select count(e1.entry_id) + from auth_batch_job_entries e1 + where e1.job_id = j.job_id) as num_actions, + (select count(e2.entry_id) + from auth_batch_job_entries e2 + where e2.job_id = j.job_id + and e2.success_p = 'f') as num_problems + from auth_batch_jobs j + where j.job_id = :job_id + + + + + + update auth_batch_jobs + set doc_start_time = sysdate + where job_id = :job_id + + + + + + update auth_batch_jobs + set doc_end_time = sysdate + doc_status = :doc_status, + doc_message = :doc_message, + document_id = :document_id + where job_id = :job_id + + + + + + + insert into auth_batch_job_entries + (entry_id, job_id, operation, authority_id, username, user_id, success_p, message, element_messages) + values + (:entry_id, :job_id, :operation, :authority_id, :username, :user_id, :success_p_db, :message, empty_clob()) + returning element_messages into :1 + + + + + + + + + Index: openacs-4/packages/acs-authentication/tcl/sync-procs-postgresql.xql =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/tcl/sync-procs-postgresql.xql,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/tcl/sync-procs-postgresql.xql 8 Sep 2003 15:59:43 -0000 1.1 @@ -0,0 +1,61 @@ + + + + postgresql7.1 + + + + select job_id, + job_start_time, + job_end_time, + interactive_p, + creation_user, + doc_start_time, + doc_end_time, + doc_status, + doc_message, + document_id, + trunc(extract(epoch from (j.job_end_time - j.job_start_time))) as run_time_seconds, + (select count(e1.entry_id) + from auth_batch_job_entries e1 + where e1.job_id = j.job_id) as num_actions, + (select count(e2.entry_id) + from auth_batch_job_entries e2 + where e2.job_id = j.job_id + and e2.success_p = 'f') as num_problems + from auth_batch_jobs j + where j.job_id = :job_id + + + + + + update auth_batch_jobs + set doc_start_time = current_timestamp + where job_id = :job_id + + + + + + update auth_batch_jobs + set doc_end_time = current_timestamp, + doc_status = :doc_status, + doc_message = :doc_message, + document_id = :document_id + where job_id = :job_id + + + + + + + insert into auth_batch_job_entries + (entry_id, job_id, operation, authority_id, username, user_id, success_p, message, element_messages) + values + (:entry_id, :job_id, :operation, :authority_id, :username, :user_id, :success_p_db, :message, :element_messages) + + + + + Index: openacs-4/packages/acs-authentication/tcl/sync-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/tcl/sync-procs.tcl,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/tcl/sync-procs.tcl 8 Sep 2003 15:59:43 -0000 1.1 @@ -0,0 +1,360 @@ +ad_library { + API for managing synchronization of user data. + + @creation-date 2003-09-05 + @author Lars Pind (lars@collaboraid.biz) + @cvs-id $Id: sync-procs.tcl,v 1.1 2003/09/08 15:59:43 lars Exp $ +} + +namespace eval auth {} +namespace eval auth::sync {} +namespace eval auth::sync::job {} + + + +ad_proc -public auth::sync::job::get { + {-job_id:required} + {-array:required} + {-include_document:boolean} +} { + Get information about a batch job in an array. + + @param job_id The ID of the batch job you're ending. + + @param array Name of an array into which you want the information. + + @param include_document + Set this switch if you also want the document returned in the array. + + @author Lars Pind (lars@collaboraid.biz) +} { + upvar 1 $array row + + db_1row select_job {} -column_array row + + # TODO: This is temporary, make sure this is where the UI ends up + set row(log_url) [export_vars -base "[ad_url]/acs-admin/package/acs-authentication/sync-log" { job_id }] + + if { $include_document_p } { + # TODO: Return the document once we know how we'll store it + set job(document) "Not implemented" + } +} + +ad_proc -public auth::sync::job::start { + {-job_id ""} + {-authority_id:required} + {-interactive:boolean} + {-creation_user ""} +} { + Record the beginning of a job. + + @param authority_id The ID of the authority you're trying to sync + + @param interactive Set this if this is an interactive job, i.e. it's initiated by a user. + + @return job_id An ID for the new batch job. Used when calling other procs in this API. + + @author Lars Pind (lars@collaboraid.biz) +} { + db_transaction { + if { [empty_string_p $job_id] } { + set job_id [db_nextval "auth_batch_jobs_job_id_seq"] + } + + if { $interactive_p && [empty_string_p $creation_user] } { + set creation_user [ad_conn user_id] + } + + db_dml job_insert { + insert into auth_batch_jobs + (job_id, interactive_p, creation_user) + values + (:job_id, :interactive_p, :creation_user) + } + } + + return $job_id +} + +ad_proc -public auth::sync::job::end { + {-job_id:required} +} { + Record the end of a batch job. Closes out the transaction + log and sends out notifications. + + @param job_id The ID of the batch job you're ending. + + @return array list with result of auth::sync::job::get. + + @see auth::sync::job::get + + @author Lars Pind (lars@collaboraid.biz) +} { + db_dml set_end_time { + update auth_batch_jobs + set job_end_time = current_timestamp + where job_id = :job_id + } + + # interactive_p, run_time_seconds, num_actions, num_problems + get -job_id $job_id -array job + + if { ![template::util::is_true $job(interactive_p)] } { + # Only send out email if not an interactive job + + with_catch errmsg { + ns_sendmail \ + [ad_system_owner] \ + [ad_system_owner] \ + "Batch sync completed" \ + "Batch user synchronization is complete.\n\nRunning time: $run_time_seconds seconds\nNumber of actions: $num_actions\nNumber of problems: $num_problems\n\nTo view the log, please visit\n$view_log_url" + } { + # We don't fail hard here, just log an error + global errorInfo + ns_log Error "Error sending registration confirmation to [ad_system_owner].\n$errorInfo" + } + } + + return [array get job] +} + +ad_proc -public auth::sync::job::start_get_document { + {-job_id:required} +} { + Record the that we're starting to get the document. + + @param job_id The ID of the batch job you're ending. +} { + db_dml update_doc_start_time {} +} + +ad_proc -public auth::sync::job::end_get_document { + {-job_id:required} + {-doc_status:required} + {-doc_message ""} + {-document ""} +} { + Record the that we've finished getting the document, and record the status. + + @param job_id The ID of the batch job you're ending. +} { + # TODO: Create cr_item containing document. Talk to Jun Kyamog about a CR API + set document_id {} + + db_dml update_doc_end {} +} + +ad_proc -public auth::sync::job::create_entry { + {-job_id:required} + {-operation:required} + {-authority_id:required} + {-username:required} + {-user_id ""} + {-success:boolean} + {-message ""} + {-element_messages ""} +} { + Record a batch job entry. + + @param job_id The ID of the batch job you're ending. + + @param operation One of 'insert', 'update', or 'delete'. + + @param authority_id The authority this is about + + @param username The username of the user being inserted/updated/deleted. + + @param user_id The user_id of the local user account, if known. + + @param success Whether or not the operation went well. + + @param message Any error message to stick into the log. + + @return entry_id +} { + set success_p_db [ad_decode $success_p 1 "t" "f"] + + set entry_id [db_nextval "auth_batch_job_entry_id_seq"] + + db_dml insert_entry {} -clobs [list $element_messages] + + return $entry_id +} + +ad_proc -public auth::sync::job::get_entry { + {-entry_id:required} + {-array:required} +} { + Get information about a log entry +} { + upvar 1 $array row + + db_1row select_entry { + select entry_id, + job_id, + entry_time, + operation, + authority_id, + username, + user_id, + success_p, + message, + element_messages + from auth_batch_job_entries + where entry_id = :entry_id + } -column_array row +} + + +ad_proc -public auth::sync::job::action { + {-job_id:required} + {-operation:required} + {-authority_id:required} + {-username:required} + {-first_names ""} + {-last_name ""} + {-email ""} + {-url ""} + {-portrait_url ""} +} { + Inserts/updates/deletes a user, depending on the operation. + + @param job_id The job which this is part of for logging purposes. + + @param operation 'insert', 'update', 'delete', or 'snapshot'. + + @param authority_id The authority involved + + @param username The username which this action refers to. + + @return entry_id of newly created entry +} { + set entry_id {} + set user_id {} + + db_transaction { + # We deal with insert/update in a snaphsot sync here + if { [string equal $operation "snapshot"] } { + set user_id [acs_user::get_by_username \ + -authority_id $authority_id \ + -username $username] + + if { ![empty_string_p $user_id] } { + # user exists, it's an update + set operation "update" + } else { + # user does not exist, it's an insert + set operation "insert" + } + } + + set success_p 1 + array set result { + message {} + element_messages {} + } + + with_catch errmsg { + switch $operation { + "insert" { + # We set email_verified_p to 't', because we trust the email we get from the remote system + array set result [auth::create_local_account \ + -authority_id $authority_id \ + -username $username \ + -first_names $first_names \ + -last_name $last_name \ + -email $email \ + -email_verified_p "t" \ + -url $url] + + if { ![string equal $result(creation_status) "ok"] } { + set result(message) $result(creation_message) + set success_p 0 + } + + # We ignore account_status + } + "update" { + # We set email_verified_p to 't', because we trust the email we get from the remote system + array set result [auth::update_local_account \ + -authority_id $authority_id \ + -username $username \ + -first_names $first_names \ + -last_name $last_name \ + -email $email \ + -email_verified_p "t" \ + -url $url] + + if { ![string equal $result(update_status) "ok"] } { + set result(message) $result(update_message) + set success_p 0 + } + } + "delete" { + array set result [auth::delete_local_account \ + -authority_id $authority_id \ + -username $username] + + if { ![string equal $result(delete_status) "ok"] } { + set result(message) $result(delete_message) + set success_p 0 + } + } + } + } { + # Get errorInfo and log it + global errorInfo + ns_log Error "Error during batch syncrhonization job:\n$errorInfo" + set success_p 0 + set result(message) $errorInfo + } + + # Make a log entry + set entry_id [auth::sync::job::create_entry \ + -job_id $job_id \ + -operation $operation \ + -authority_id $authority_id \ + -username $username \ + -user_id $user_id \ + -success=$success_p \ + -message $result(message) \ + -element_messages $result(element_messages)] + } + + return $entry_id +} + +ad_proc -public auth::sync::job::snapshot_delete_remaining { + -job_id:required + -authority_id:required +} { + Deletes the users that weren't included in the snapshot. +} { + # run through users that belong to the given authority, + # but which don't already have an entry in the log + # and call auth::sync::job::action -operation "delete" on them + + # TODO +} + + + + + + +ad_proc -public auth::sync::purge_jobs {} { + Purge jobs that are older than KeepBatchLogDays days. +} { + # Don't forget to also delete the cr_item referenced by the auth_batch_jobs.document column. + + # Parameter: KeepBatchLogDays - number of days to keep batch job log around. 0 = forever. + + # TODO +} + + + + + + Index: openacs-4/packages/acs-authentication/tcl/test/acs-authentication-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/tcl/test/acs-authentication-procs.tcl,v diff -u -N -r1.20 -r1.21 --- openacs-4/packages/acs-authentication/tcl/test/acs-authentication-procs.tcl 5 Sep 2003 14:40:10 -0000 1.20 +++ openacs-4/packages/acs-authentication/tcl/test/acs-authentication-procs.tcl 8 Sep 2003 15:59:43 -0000 1.21 @@ -167,31 +167,62 @@ } - # Missing first_names + # Missing first_names, last_name, email array unset user_info array set user_info [auth::create_user \ -username "auth_create_user2" \ - -email "auth_create_user2@test_user.com" \ + -email "" \ -first_names "" \ - -last_name "User" \ + -last_name "" \ -password "changeme" \ -secret_question "no_question" \ -secret_answer "no_answer"] - aa_equals "creation_status for missing first names" $user_info(creation_status) "data_error" + aa_equals "creation_status is data_error" $user_info(creation_status) "data_error" aa_true "element_messages exists" [exists_and_not_null user_info(element_messages)] if { [exists_and_not_null user_info(element_messages)] } { array unset elm_msgs array set elm_msgs $user_info(element_messages) - aa_true "element_message for first_names exists" [exists_and_not_null elm_msgs(first_names)] - + + if { [aa_true "element_message(email) exists" [exists_and_not_null elm_msgs(email)]] } { + aa_log "element_message(email) = $elm_msgs(email)" + } + if { [aa_true "element_message(first_names) exists" [exists_and_not_null elm_msgs(first_names)]] } { + aa_log "element_message(first_names) = $elm_msgs(first_names)" + } + if { [aa_true "element_message(last_name) exists" [exists_and_not_null elm_msgs(last_name)]] } { + aa_log "element_message(last_name) = $elm_msgs(last_name)" + } } - if { [info exists user_info(element_messages)] } { - array set element_message $user_info(element_messages) - aa_log "user_info(element_messages) = '$user_info(element_messages)'" - aa_true "Element message for first_names exists" [exists_and_not_null element_message(first_names)] + # Malformed email + array unset user_info + array set user_info [auth::create_user \ + -username [ad_generate_random_string] \ + -email "not an email" \ + -first_names "[ad_generate_random_string]<[ad_generate_random_string]" \ + -last_name "[ad_generate_random_string]<[ad_generate_random_string]" \ + -password [ad_generate_random_string] \ + -secret_question [ad_generate_random_string] \ + -secret_answer [ad_generate_random_string]] + + aa_equals "creation_status is data_error" $user_info(creation_status) "data_error" + + aa_true "element_messages exists" [exists_and_not_null user_info(element_messages)] + if { [exists_and_not_null user_info(element_messages)] } { + array unset elm_msgs + array set elm_msgs $user_info(element_messages) + + if { [aa_true "element_message(email) exists" [exists_and_not_null elm_msgs(email)]] } { + aa_log "element_message(email) = $elm_msgs(email)" + } + if { [aa_true "element_message(first_names) exists" [exists_and_not_null elm_msgs(first_names)]] } { + aa_log "element_message(first_names) = $elm_msgs(first_names)" + } + if { [aa_true "element_message(last_name) exists" [exists_and_not_null elm_msgs(last_name)]] } { + aa_log "element_message(last_name) = $elm_msgs(last_name)" + } } # Duplicate email and username Index: openacs-4/packages/acs-authentication/tcl/test/sync-test-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/acs-authentication/tcl/test/sync-test-procs.tcl,v diff -u -N --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openacs-4/packages/acs-authentication/tcl/test/sync-test-procs.tcl 8 Sep 2003 15:59:43 -0000 1.1 @@ -0,0 +1,177 @@ +ad_library { + Automated tests for synchronization API + + @author Lars Pind (lars@collaboraid.biz) + @creation-date 05 September 2003 + @cvs-id $Id: sync-test-procs.tcl,v 1.1 2003/09/08 15:59:43 lars Exp $ +} + +aa_register_case job_start_end { + Test starting and ending a batch job +} { + aa_run_with_teardown \ + -rollback \ + -test_code { + + + # TODO: Add checks that the user info actually look the way it's supposed to ... use the acs_user::get API to verify + + + + # Start non-interactive job + + set job_id [auth::sync::job::start \ + -authority_id [auth::authority::local]] + + aa_true "Returns a job_id" [exists_and_not_null job_id] + + + # Get doc + auth::sync::job::start_get_document -job_id $job_id + + auth::sync::job::end_get_document \ + -job_id $job_id \ + -doc_status "ok" \ + -doc_message "" \ + -document {} + + # Valid successful log entry + auth::sync::job::create_entry \ + -job_id $job_id \ + -operation "insert" \ + -authority_id [auth::authority::local] \ + -username "foobar" \ + -user_id [ad_conn user_id] \ + -success + + # Valid unsuccessful log entry + auth::sync::job::create_entry \ + -job_id $job_id \ + -operation "insert" \ + -authority_id [auth::authority::local] \ + -username "foobar" \ + -user_id [ad_conn user_id] \ + -message "A problem" \ + -element_messages "" + + # Valid insert action + set username1 [ad_generate_random_string] + set email1 "[ad_generate_random_string]@foo.bar" + aa_log "--- Valid insert --- auth::sync::job::action -opration insert -username $username1 -email $email1" + set entry_id [auth::sync::job::action \ + -job_id $job_id \ + -operation "insert" \ + -authority_id [auth::authority::local] \ + -username $username1 \ + -first_names [ad_generate_random_string] \ + -last_name [ad_generate_random_string] \ + -email $email1] + + array unset entry + auth::sync::job::get_entry \ + -entry_id $entry_id \ + -array entry + + aa_equals "entry.success_p" $entry(success_p) "t" + aa_equals "entry.message" $entry(message) {} + aa_equals "entry.element_messages" $entry(element_messages) {} + aa_log "entry.user_id = '$entry(user_id)'" + aa_log "entry.message = '$entry(message)'" + aa_log "entry.element_messages = '$entry(element_messages)'" + + # Invalid insert action: Reusing username, email + aa_log "--- Invalid insert: reusing username, email --- auth::sync::job::action -opration insert -username $username1 -email $email1" + set entry_id [auth::sync::job::action \ + -job_id $job_id \ + -operation "insert" \ + -authority_id [auth::authority::local] \ + -username $username1 \ + -first_names [ad_generate_random_string] \ + -last_name [ad_generate_random_string] \ + -email $email1] + + array unset entry + auth::sync::job::get_entry \ + -entry_id $entry_id \ + -array entry + + aa_equals "entry.success_p" $entry(success_p) "f" + + if { [aa_true "entry.element_messages not empty" [exists_and_not_null entry(element_messages)]] } { + array unset elm_msgs + array set elm_msgs $entry(element_messages) + aa_true "username, email have problems" [util_sets_equal_p { username email } [array names elm_msgs]] + } + + aa_log "entry.user_id = '$entry(user_id)'" + aa_log "entry.message = '$entry(message)'" + aa_log "entry.element_messages = '$entry(element_messages)'" + + # Valid update action + aa_log "--- Valid update --- auth::sync::job::action -opration update -username $username1" + set entry_id [auth::sync::job::action \ + -job_id $job_id \ + -operation "update" \ + -authority_id [auth::authority::local] \ + -username $username1 \ + -first_names [ad_generate_random_string] \ + -last_name [ad_generate_random_string] \ + -email "[ad_generate_random_string]@foo.bar"] + + array unset entry + auth::sync::job::get_entry \ + -entry_id $entry_id \ + -array entry + + aa_equals "entry.success_p" $entry(success_p) "t" + aa_equals "entry.message" $entry(message) {} + aa_equals "entry.element_messages" $entry(element_messages) {} + aa_log "entry.user_id = '$entry(user_id)'" + aa_log "entry.message = '$entry(message)'" + aa_log "entry.element_messages = '$entry(element_messages)'" + + + # Invalid insert action: Missing first_names, last_name invalid, email invalid + set username2 [ad_generate_random_string] + aa_log "--- Invalid insert --- auth::sync::job::action -opration update -username $username2" + set entry_id [auth::sync::job::action \ + -job_id $job_id \ + -operation "insert" \ + -authority_id [auth::authority::local] \ + -username $username2 \ + -first_names {} \ + -last_name {Foobar} \ + -email "not_an_email"] + + array unset entry + auth::sync::job::get_entry \ + -entry_id $entry_id \ + -array entry + + aa_equals "entry.success_p" $entry(success_p) "f" + aa_log "entry.message = '$entry(message)'" + if { [aa_true "entry.element_messages not empty" [exists_and_not_null entry(element_messages)]] } { + aa_log "entry.element_messages = '$entry(element_messages)'" + array unset elm_msgs + array set elm_msgs $entry(element_messages) + aa_log "array names elm_msgs = '[array names elm_msgs]'" + aa_true "first_names, last_name, email have problems" [util_sets_equal_p { first_names last_name email } [array names elm_msgs]] + } + + # End job + array set job [auth::sync::job::end -job_id $job_id] + + aa_true "Elapsed time less than 30 seconds" [expr $job(run_time_seconds) < 30] + + aa_false "Not interactive" [template::util::is_true $job(interactive_p)] + + aa_equals "Number of actions" $job(num_actions) 6 + + aa_equals "Number of problems" $job(num_problems) 3 + + aa_false "Log URL non-empty" [empty_string_p $job(log_url)] + + + } +} +