This entry moved to http://www.project-open.org/documentation/rfe_advanced_financial_project_planning
http://stackoverflow.com/questions/1973/what-is-the-best-way-to-avoid-sql-injection-attacks
Golden Rules:
3: Custom-Written Value Sanitation. Avoid writing your own sanitation routines as much as possible except when it’s absolutely the only option remaining (which is very unlikely in any modern language). Input sanitation is a hard problem, and the costs of getting it wrong are huge. It’s best to leave that job to someone else. When you are forced to rely on this method use white-listing rather than black-listing to sanitize input.
2: Framework / Library Based Value Sanitation. Leave the sanitation routines to the domain experts. Sanitation routines in 1st party frameworks and libraries are typically created by the same folks that wrote the DB or the SQL API. They have a much higher chance of knowing, and properly handling, all of the edge cases than you do. An example of this technique would be using php’s mysql_escape_string() function to sanitize values that will be inserted into strings that serve as dynamic SQL statements.
1: Parameter Binding. Instead of constructing a SQL statement as a raw string which includes user data as in-place literal values, create a SQL statement with tokens where the user data would be. Then bind the user supplied data to the appropriate parameters. The key here is that this binds the provided data to a specific type and a specific use and eliminates any opportunity to change the logic of the SQL statement. This can be used in conjunction with library based input sanitation as well.
http://www.tcl.tk/community/tcl2004/Tcl2003papers/cleverly.htm
Luckily for the Tcl programmer who craves laziness, OpenACS provides a higher-level database API on top of ns_db that hides all the mundane house-keeping chores. Additionaly, bind-variable emulation is available which frees the programmer from worrying about always escaping apostrophes in SQL statements. Bind variables are prefaced with a colon.
set name "Jim O'Connor"
set salary 40000
db_dml new_hire {
insert into employees (name, salary, dept)
values (:name, :salary, 'Accounting')
}
http://openacs.com/forums/message-view?message_id=250625
Posted by Dave Bauer on 01/19/05 04:57 PM
In Postgresql the db_* commands extract the “bind” variables and SQL quote them automatically. In the PG driver itself the [] charactaers are escaped as well for safety.
This means that the effective security of PostgreSQL is the same as Oracle.
This is done at the database api layer.
In addition ad_page_contract has filters and validation features to check input. ad_form and the form builder also have validation features for input.
xowiki pages are managed with the OpenACS Content repository.
XOWIKI pages:
For each page there’s an entry in cr_items
projop=# \d cr_items
Table "public.cr_items"
Column | Type | Modifiers
-------------------+------------------------+------------------------------------------------
item_id | integer | not null
parent_id | integer | not null
name | character varying(400) | not null
locale | character varying(4) |
live_revision | integer |
latest_revision | integer |
publish_status | character varying(40) |
content_type | character varying(100) |
storage_type | character varying(10) | not null default 'text'::character varying
storage_area_key | character varying(100) | not null default 'CR_FILES'::character varying
tree_sortkey | bit varying | not null
max_child_sortkey | bit varying |
Page content
Page content is stored in table cr_revisions
projop=# \d cr_revisions
Table "public.cr_revisions"
Column | Type | Modifiers
----------------+--------------------------+-----------------------------------------
revision_id | integer | not null
item_id | integer | not null
title | character varying(1000) |
description | text |
publish_date | timestamp with time zone |
mime_type | character varying(200) | default 'text/plain'::character varying
nls_language | character varying(50) |
lob | integer |
content | text |
content_length | integer |
Finding content that can’t be found using ‘search’ such as links etc.
select r.item_id, r.title, i.name from cr_revisions r, cr_items i where r.content like '%en:install_main%' and r.revision_id = i.live_revision;
Object Model
Pls. see: http://openacs.org/xotcl/show-object?show_source=0&object=%3A%3Axowiki%3A%3APage&show_methods=1
The package provides the following features:
- Provide a single access point for all tasks assigned to an user
- Gives users the possibility to create a sortable list of tasks
- Tasks can added from a list of existing project tasks, forum tasks and workflow tasks
- Let users add arbitrary tasks not managed with ]po[
- Provide users with quick access to often used features such as time sheet tracking and relevant task data.
- Speeds up adminstrative tasks and provides convenience to users working in multitasking environments, such as Help-Desk, Call-Center, Support, etc.
Principal architectural objectives:
- Implementing a RESTful Web Service
- Evaluate Open Source JS libraries for use in ]po[
- Security aspects and error handling when using xmlhttp requests (XHR)
Protoytype
To-Do's
- Fix Bug: time - logging
- Show err mess when no tasks have been found for project
- Allow to delete tasks from To-Do list by drag&drop them to wastebasket
- Add WF tab (shows all tasks resulting from WF instances)
- Add Forum tab (shows all FORUM tasks)
Developer Documentation
The package is available from the ]po[ CVS server ("experimental status"):
Package Name: intranet-gtd-dashboard CVSROOT: ":pserver:anonymous@cvs.openacs.org:/home/cvsroot"
It had been created in 2008/2009. The ]po[ RESTful interface was not yet available.
Table "public.im_gtd_tasks"
Column | Type | Modifiers
--------------------+--------------------------+-----------
gtd_task_id | integer | not null
ref_id | integer | not null
owner_id | integer |
created_date | timestamp with time zone |
description | text |
last_modified | date |
gtd_type_id | integer | not null
gtd_task_status_id | integer | not null
Indexes:
"im_gtd_tasks_pkey" PRIMARY KEY, btree (gtd_task_id)
Foreign-key constraints:
"im_gtd_task_type_fk" FOREIGN KEY (gtd_type_id) REFERENCES im_categories(category_id)
"im_gtd_task_type_status_fk" FOREIGN KEY (gtd_task_status_id) REFERENCES im_categories(category_id)
"im_gtd_tasks_owner_id_fkey" FOREIGN KEY (owner_id) REFERENCES users(user_id)
Content
- General Information
- Tables and views used
- Time sheet Portlets
- Sample Queries
General information
When time sheet form (/intranet-timesheet2/hours/new) is send to the server,
- … ]po[ deletes affected time sheet entries for affected day and user and creates new ones
- ... ]po[ updates im_projects::reported_hours_cache
Please note that
- ... Paramaters have in impact on values shown in time sheet plugins. In case there's mismath regarding hours reported, check parameter SyncHoursInterval and SyncHoursP
- If hours don't sum up correctly on the time sheet component in a sub project, there might be hours logged on the main project that are not considered.
fullquery name: update_timesheet_task
update im_projects
set reported_hours_cache = (
select sum(h.hours)
from im_hours h
where h.project_id = :project_id
)
where project_id = :project_id
fullquery name: delete_timesheet_costs
DECLARE
row RECORD;
BEGIN
for row in
select cost_id
from im_costs
where cost_type_id = [im_cost_type_timesheet]
and project_id = :project_id
and cause_object_id = :user_id_from_search
and effective_date = to_date(:julian_date, ‘J’)
loop
PERFORM im_cost__delete(row.cost_id);
end loop;
return 0;
END;
Tables used
- im_timesheet_tasks
- im_hours
- im_costs
im_timesheet_tasks
- Contains all timesheet tasks
projop=# \d im_timesheet_tasks
Table "public.im_timesheet_tasks"
Column | Type | Modifiers
----------------+------------------+-----------
task_id | integer | not null
material_id | integer | not null
uom_id | integer | not null
planned_units | double precision |
billable_units | double precision |
cost_center_id | integer |
invoice_id | integer |
priority | integer |
sort_order | integer |
bt_bug_id | integer |
Indexes:
"im_timesheet_tasks_pk" PRIMARY KEY, btree (task_id)
Foreign-key constraints:
"im_times_tasks_bt_bug_fk" FOREIGN KEY (bt_bug_id) REFERENCES bt_bugs(bug_id)
"im_timesheet_task_fk" FOREIGN KEY (task_id) REFERENCES im_projects(project_id)
"im_timesheet_tasks_cost_center_fk" FOREIGN KEY (cost_center_id) REFERENCES im_cost_centers(cost_center_id)
"im_timesheet_tasks_invoice_fk" FOREIGN KEY (invoice_id) REFERENCES im_invoices(invoice_id)
"im_timesheet_tasks_material_fk" FOREIGN KEY (material_id) REFERENCES im_materials(material_id)
"im_timesheet_tasks_uom_fk" FOREIGN KEY (uom_id) REFERENCES im_categories(category_id)
im_costs
A cost item will be created for each time sheet entry
Table "public.im_costs"
Column | Type | Modifiers
------------------------+--------------------------+-----------
cost_id | integer | not null
cost_name | character varying(400) | not null
cost_nr | character varying(400) | not null
project_id | integer |
customer_id | integer | not null
cost_center_id | integer |
provider_id | integer | not null
investment_id | integer |
cost_status_id | integer | not null
cost_type_id | integer | not null
cause_object_id | integer |
template_id | integer |
effective_date | timestamp with time zone |
start_block | timestamp with time zone |
payment_days | integer |
amount | numeric(12,3) |
currency | character(3) |
paid_amount | numeric(12,3) |
paid_currency | character(3) |
vat | numeric(12,5) |
tax | numeric(12,5) |
variable_cost_p | character(1) |
needs_redistribution_p | character(1) |
parent_id | integer |
redistributed_p | character(1) |
planning_p | character(1) |
planning_type_id | integer |
description | character varying(4000) |
note | character varying(4000) |
last_modified | timestamp with time zone |
last_modifying_user | integer |
last_modifying_ip | character varying(20) |
Indexes:
"im_costs_pk" PRIMARY KEY, btree (cost_id)
"im_costs_cause_object_idx" btree (cause_object_id)
"im_costs_start_block_idx" btree (start_block)
Check constraints:
"im_costs_needs_redist_ck" CHECK (needs_redistribution_p = 't'::bpchar OR needs_redistribution_p = 'f'::bpchar)
"im_costs_planning_ck" CHECK (planning_p = 't'::bpchar OR planning_p = 'f'::bpchar)
"im_costs_redist_ck" CHECK (redistributed_p = 't'::bpchar OR redistributed_p = 'f'::bpchar)
"im_costs_var_ck" CHECK (variable_cost_p = 't'::bpchar OR variable_cost_p = 'f'::bpchar)
Foreign-key constraints:
"im_cost_template_fk" FOREIGN KEY (template_id) REFERENCES im_categories(category_id)
"im_costs_cause_fk" FOREIGN KEY (cause_object_id) REFERENCES acs_objects(object_id)
"im_costs_cost_center_fk" FOREIGN KEY (cost_center_id) REFERENCES im_cost_centers(cost_center_id)
"im_costs_cost_fk" FOREIGN KEY (cost_id) REFERENCES acs_objects(object_id)
"im_costs_currency_fk" FOREIGN KEY (currency) REFERENCES currency_codes(iso)
"im_costs_customer_fk" FOREIGN KEY (customer_id) REFERENCES acs_objects(object_id)
"im_costs_inv_fk" FOREIGN KEY (investment_id) REFERENCES acs_objects(object_id)
"im_costs_last_mod_user" FOREIGN KEY (last_modifying_user) REFERENCES users(user_id)
"im_costs_paid_currency_fk" FOREIGN KEY (paid_currency) REFERENCES currency_codes(iso)
"im_costs_parent_fk" FOREIGN KEY (parent_id) REFERENCES im_costs(cost_id)
"im_costs_planning_type_fk" FOREIGN KEY (planning_type_id) REFERENCES im_categories(category_id)
"im_costs_provider_fk" FOREIGN KEY (provider_id) REFERENCES acs_objects(object_id)
"im_costs_status_fk" FOREIGN KEY (cost_status_id) REFERENCES im_categories(category_id)
"im_costs_type_fk" FOREIGN KEY (cost_type_id) REFERENCES im_categories(category_id)
Triggers:
im_costs_project_cache_del_tr AFTER DELETE ON im_costs FOR EACH ROW EXECUTE PROCEDURE im_cost_project_cache_del_tr()
im_costs_project_cache_ins_tr AFTER INSERT ON im_costs FOR EACH ROW EXECUTE PROCEDURE im_cost_project_cache_ins_tr()
im_costs_project_cache_up_tr AFTER UPDATE ON im_costs FOR EACH ROW EXECUTE PROCEDURE im_cost_project_cache_up_tr()
Views used by ]po[ Time Sheet Management
- im_timesheet_tasks_view
projop=# \d im_timesheet_tasks_view
View "public.im_timesheet_tasks_view"
Column | Type | Modifiers
----------------------+--------------------------+-----------
task_id | integer |
material_id | integer |
uom_id | integer |
planned_units | double precision |
billable_units | double precision |
cost_center_id | integer |
invoice_id | integer |
priority | integer |
sort_order | integer |
project_id | integer |
task_name | character varying(1000) |
task_nr | character varying(100) |
percent_completed | double precision |
task_type_id | integer |
task_status_id | integer |
start_date | timestamp with time zone |
end_date | timestamp with time zone |
reported_hours_cache | double precision |
reported_units_cache | double precision |
View definition:
SELECT t.task_id, t.material_id, t.uom_id, t.planned_units, t.billable_units, t.cost_center_id, t.invoice_id, t.priority, t.sort_order, p.parent_id AS project_id, p.project_name AS task_name, p.project_nr AS task_nr, p.percent_completed, p.project_type_id AS task_type_id, p.project_status_id AS task_status_id, p.start_date, p.end_date, p.reported_hours_cache, p.reported_hours_cache AS reported_units_cache
FROM im_projects p, im_timesheet_tasks t
WHERE t.task_id = p.project_id;
Time sheet Portlets
Timesheet Tasks
| Location: | /intranet/projects/view?project_id=... |
Facts about the Time sheet Component:
- In order to speed up page creation, hours are taken from im_projects::reported_hours_cache
Samples
Get all ‘childs’
select p_child.project_id from im_projects p_parent, im_projects p_child where p_child.tree_sortkey between p_parent.tree_sortkey and tree_right(p_parent.tree_sortkey) and p_parent.project_id = 16030;
Get top parent project
select main_p.project_id
from im_projects p,
im_projects main_p
where p.project_id = :project_id and
tree_ancestor_key(p.tree_sortkey, 1) = main_p.tree_sortkey
Formatting Date Fields
to_char(u.last_visit, 'YYYY-MM-DD HH:SS') as last_visit_formatted,
All dates are in general of type timestamp which allows the the following operations:
Check if date lies before or after a reference date:p.start_date < '2006-04-04'
Get value name for categories
select
im_category_from_id(t.target_language_id) as target_language,
im_category_from_id(t.source_language_id) as source_language
from
im_trans_tasks t,
where
and t.project_id = 123;
coalesce
select sum(coalesce(s.billable_units,0)) as task_sum, trim(both ' ' from to_char(t.task_sum, :'999999999999D')) as task_sum_ from im_trans_tasks;
CASE
select
p.project_name as sub_project_name,
p.project_nr as sub_project_nr,
p.project_type_id as sub_project_type_id,
p.project_status_id as sub_project_status_id,
tree_ancestor_key(p.tree_sortkey, 1) as main_project_sortkey,
trunc((c.paid_amount *
im_exchange_rate(c.effective_date::date, c.currency, 'EUR')) :: numeric
, 2) as paid_amount_converted,
CASE c.cost_type_id = 3718
WHEN true THEN
TO_CHAR(trunc((c.amount * im_exchange_rate(c.effective_date::date, c.currency, 'EUR') * '90' / 100 ) :: numeric, 2),‘LFM99G999G990D00')
ELSE
trunc((c.amount * im_exchange_rate(c.effective_date::date, c.currency, 'EUR')) :: numeric, 2)
END as amount_converted,
c.*
from
im_costs c
LEFT OUTER JOIN im_projects p ON (c.project_id = p.project_id)
where
NOT (c.customer_id = 28148 AND c.cost_type_id = 3718)
and c.cost_type_id in (3704, 3706, 3718, 3720, 3700)
and c.effective_date::date >= to_date('2009-09-28', 'YYYY-MM-DD')
and c.effective_date::date < to_date('2099-12-31', 'YYYY-MM-DD')
and c.effective_date::date < to_date('2099-12-31', 'YYYY-MM-DD')
and p.project_status_id in (76, 78, 10000132)
LOOPs
CREATE OR REPLACE FUNCTION im_workflow_NAME__cleanup(INTEGER, text, text) RETURNS INTEGER AS ' declare p_case_id alias for $1; p_transition_key alias for $2; p_custom_arg alias for $3; v_task_id integer; v_case_id integer; v_object_id integer; v_creation_user integer; v_creation_ip varchar; v_journal_id integer; v_transition_key varchar; v_workflow_key varchar; r record; begin v_user_id := p_custom_arg; -- Select out some frequently used variables of the environment select c.object_id, c.workflow_key, task_id, c.case_id into v_object_id, v_workflow_key, v_task_id, v_case_id from wf_tasks t, wf_cases c where c.case_id = p_case_id and t.case_id = c.case_id and t.workflow_key = c.workflow_key and t.transition_key = p_transition_key; FOR r IN select request_id from notification_requests where object_id = v_object_id and user_id = v_user_id LOOP select notification_request__delete(r.request_id); perform acs_object__delete(r.request_id); END LOOP; end;' LANGUAGE 'plpgsql' VOLATILE;
Frame for disposable functions
CREATE OR REPLACE FUNCTION inline_0 () RETURNS INTEGER AS ' declare v_count integer; begin select count(*) into v_count from im_categories where category_id = 5006 or category_id = 5007; IF 0 != v_count THEN RAISE NOTICE ''upgrade-4.0.1.0.0-4.0.1.0.1.sql failed - could not add categories''; return 0; END IF; PERFORM im_category_new(5006, ''Overtime'', ''Intranet Absence Type''); PERFORM im_category_new(5007, ''Reduction in Working Hours'', ''Intranet Absence Type''); return 1; end;' LANGUAGE 'plpgsql'; SELECT inline_0 (); DROP FUNCTION inline_0 ();
Using the $body$ tag
create or replace function im_name_from_user_id(int4, int4) returns varchar as $body$
DECLARE
v_user_id alias for $1;
v_name_order alias for $2;
v_full_name varchar(8000);
BEGIN
IF 2 == v_name_order THEN
select last_names || ' ' || first_name
into v_full_name
from persons
where person_id = v_user_id;
ELSEIF 3 == v_name_order THEN
select last_names || ', ' || first_name
into v_full_name
from persons
where person_id = v_user_id;
ELSE
select first_names || ' ' || last_name
into v_full_name
from persons
where person_id = v_user_id;
END IF;
return v_full_name;
END;$body$ language 'plpgsql';
Check for empty string
IF v_type_category_type IS NULL or length(v_type_category_type) = 0
THEN
update acs_object_types set type_category_type = ''Intranet SLA Parameter Type'' where object_type = ''im_sla_parameter'';
END IF;
Auto ID's
create sequence im_customer_project_type_seq;
create table im_customer_project_type (
id integer,
company_id integer
references im_companies,
project_type_id integer not null,
unique(company_id, project_type_id)
);
ALTER TABLE im_customer_project_type ALTER COLUMN id SET DEFAULT NEXTVAL('im_customer_project_type_seq');
Reconstructor uses the Desktop(Live), Alternate(Install), or Server disc as a base, and then allows for user customization.
For the Ubuntu Desktop base, you can customize the entire environment. For instance, you can add/remove software, change the default look (splash, themes, fonts, wallpaper, etc.), add desktop links, etc.
For the Alternate and Server bases, you can add any additional software to the disc that you would like installed.
Reconstructor is written in python and is licensed under the GNU General Public License (GPL).
One thing we have learned today is that going to AJAX requires also a review of current HTML. Even though most pages show only warnings when HTML is validated, they are still not conform when using script libraries.
In our example we’d like extend the Home Page Project Component to introduce tabs. “open” projects and “potential” projects should be shown in the same component, accessible through tabs. In order to implement tabs we are using the YUI TabView Control.
Here’s the current code to create the main table that holds the table of projects (intranet-component-procs.tcl):
Implementation is done as follows:
This results in:

As we see, Tab Two Content is visible, clicking on the tabs also results in undesired behaviour.
Firebugs shows us the following code:

FF renders based on the definition for each element an additional font tags which then breaks the JS code.