diff --git a/modules/webaccess/doc/hacking/api.html.wml b/modules/webaccess/doc/hacking/api.html.wml index fa9d24542..c1b7b20de 100644 --- a/modules/webaccess/doc/hacking/api.html.wml +++ b/modules/webaccess/doc/hacking/api.html.wml @@ -1,160 +1,160 @@ ## $Id$ ## This file is part of the CERN Document Server Software (CDSware). ## Copyright (C) 2002 CERN. ## ## The CDSware is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## The CDSware is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with CDSware; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. #include "cdspage.wml" \ title="Access Control Engine API" \ navbar_name="hacking-webaccess" \ navtrail_previous_links="<a class=navtrail href=<WEBURL>/hacking/>Hacking CDSware</a> > <a class=navtrail href=<WEBURL>/hacking/webaccess/index.html>WebAccess Internals</a> " \ navbar_select="hacking-webaccess-engine-api" <p>Version <: print generate_pretty_revision_date_string('$Id$'); :> <pre> CDSware Access Control Engine can be called from within your Python programs via both a regular Python API and CLI. In addition the you get an explanation of the program flow. Contents: 1. Regular API 2. Command Line Interface 3. Program Flow 1. Regular API Description: There is not very much information in the database at the moment. More can be added on demand. Information on these will be added when time allows it. Signature: def acc_authorize_action(id_user, name_action, dict={}, **arguments): """ Check if user is allowed to perform action with given list of arguments. Return (0, message) if authentication succeeds, (error_code, error_message) if it fails. The arguments are as follows: id_user - id of the user to be authorized in the database name_action - the name of the action arguments - dictionary with keyword=value pairs created automatically by python on the extra arguments. these depend on the given action. """ Examples: >>> # import the functions from module >>> # change this to your local settings... >>> pylibdir = '/log/cdsware-DEMODEV/lib/python/cdsware/' >>> import sys >>> sys.path.append('%s' % pylibdir) >>> from access_control_engine import * >>> # authorize user 109 for action WebSearch_search with collection="LHC" >>> acc_authorize_action(109, 'cfgwebsearch', collection="LHC") (0, "User authorized") >>> # authorize user 109 for action WebSearch_search with collection="fail this" >>> acc_authorize_action(109, 'cfgwebsearch', collection="fail this") (8, "Error (8): Incorrect keyword given for specified action.") >>> # authorize user 109 for action BibFormat_modify with format="htmlbrief" >>> acc_authorize_action(109, 'cfgbibformat', format="htmlbrief") (0, "User authorized") 2. Command Line Interface Description: The Command Line Interface uses the regular API of acc_authorize_action. Signature: authaction id_user name_action keyword1 value1 keyword2 value2 ... """ See description from function acc_authorize_action. id_user - id of user to be authorized name_action - name of the action keyword1 - first keyword like in the keyword=value pairs, same rules for the following ones. always one word. value1 - value that belongs in a pair with the corresponding keyword, same rules for the following ones. add quotes if it is more that one word. the keyword=value pairs are collected in a dictionary """ Examples: These are the same ones as for the regular API: $ authaction 109 cfgwebsearch collection LHC 0 - User authorized $ authaction 109 cfgwebsearch collection 'fail this' 8 - Error (8): Incorrect keyword given for specified action. $ authaction 109 cfgbibformat format htmlbrief 0 - User authorized 3. Program Flow this is a quick explanation of the different tasks performed by the authorization engine. I. find information for the action use admin API to find info. II. see if user is a superadmin query the database for connection between user and role superadmin. -> authorize if yes - III. find all of the users roles and create string with the ids + III. check if the user exists and find all of the users roles and create string with the ids query the database and build string of ids -> don't authorize if no roles IV. try to authorize without arguments action without arguments: query database -> authorize if yes action with optional arguments -> authorize if yes V. create list of keyword=value pairs to query the database run through dictionary and create string for adding to database query VI. find all table entries from the database query the database for table entries create list of the tuples and sort it -> don't authorize if no entries -> authorize if only 1 argument and result VII. combine entries and try to satisfy authorization dictionary with the arguments as keys, all values 0 run throught the list created in VI if moving on to new authorization check dictionary values -> authorize if combination found reset values to 0 if not found set dictionary[keyword] to 1. (countinue loop) VIII. all the above failed -> authorization failed </pre> diff --git a/modules/webaccess/lib/access_control_engine.py b/modules/webaccess/lib/access_control_engine.py index d4e3a761c..526937f37 100644 --- a/modules/webaccess/lib/access_control_engine.py +++ b/modules/webaccess/lib/access_control_engine.py @@ -1,220 +1,225 @@ ## $Id$ ## CDSware Access Control Engine in mod_python. ## This file is part of the CERN Document Server Software (CDSware). ## Copyright (C) 2002 CERN. ## ## The CDSware is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## The CDSware is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with CDSware; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """CDSware Access Control Engine in mod_python.""" <protect> ## okay, rest of the Python code goes below ####### __version__ = "$Id$" ## import interesting modules: from config import * from dbquery import run_sql from MySQLdb import ProgrammingError from access_control_config import SUPERADMINROLE, cfg_webaccess_warning_msgs, cfg_webaccess_msgs called_from = 1 #1=web,0=cli try: import _apache except ImportError, e: called_from = 0 ## access controle engine function def acc_authorize_action(id_user, name_action, verbose=0, **arguments): """Check if user is allowed to perform action with given list of arguments. Return (0, message) if authentication succeeds, (error code, error message) if it fails. The arguments are as follows: id_user - id of the user in the database name_action - the name of the action arguments - dictionary with keyword=value pairs created automatically by python on the extra arguments. these depend on the given action. """ # TASK 0: find id and allowedkeywords of action if verbose: print 'task 0 - get action info' query1 = """select a.id, a.allowedkeywords, a.optional from accACTION a where a.name = '%s'""" % (name_action) try: id_action, aallowedkeywords, optional = run_sql(query1)[0] except (ProgrammingError, IndexError): return (3, "%s %s" % (cfg_webaccess_warning_msgs[3] % name_action, (called_from and cfg_webaccess_msgs[1] or ""))) defkeys = aallowedkeywords.split(',') for key in arguments.keys(): if key not in defkeys: return (8, "%s %s" % (cfg_webaccess_warning_msgs[8], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) #incorrect arguments? # ------------------------------------------- # TASK 1: check if user is a superadmin # we know the action exists. no connection with role is necessary # passed arguments must have allowed keywords # no check to see if the argument exists if verbose: print 'task 1 - is user %s' % (SUPERADMINROLE, ) if run_sql("""SELECT * FROM accROLE r LEFT JOIN user_accROLE ur ON r.id = ur.id_accROLE WHERE r.name = '%s' AND ur.id_user = '%s' """ % (SUPERADMINROLE, id_user)): return (0, cfg_webaccess_warning_msgs[0]) # ------------------------------------------ - # TASK 2: find all the user's roles and create or-string - if verbose: print 'task 2 - find userroles' - - query2 = """SELECT ur.id_accROLE FROM user_accROLE ur WHERE ur.id_user=%s ORDER BY ur.id_accROLE """ % id_user - try: res2 = run_sql(query2) + # TASK 2: check if user exists and find all the user's roles and create or-string + if verbose: print 'task 2 - find user and userroles' + + try: + query2 = """SELECT * from user where id=%s""" % id_user + res2 = run_sql(query2) + if not res2: + raise Exception + query2 = """SELECT ur.id_accROLE FROM user_accROLE ur WHERE ur.id_user=%s ORDER BY ur.id_accROLE """ % id_user + res2 = run_sql(query2) except Exception: return (6, "%s %s" % (cfg_webaccess_warning_msgs[6], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) if not res2: return (2, "%s %s" % (cfg_webaccess_warning_msgs[2], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) #user has no roles # ------------------------------------------- # create role string (add default value? roles='(raa.id_accROLE='def' or ') str_roles = '' for (role, ) in res2: if str_roles: str_roles += ',' str_roles += '%s' % (role, ) # TASK 3: authorizations with no arguments given if verbose: print 'task 3 - checks with no arguments' if not arguments: # 3.1 if optional == 'no': if verbose: print ' - action with zero arguments' connection = run_sql("""SELECT * FROM accROLE_accACTION_accARGUMENT WHERE id_accROLE IN (%s) AND id_accACTION = %s AND argumentlistid = 0 AND id_accARGUMENT = 0 """ % (str_roles, id_action)) if connection and 1: return (0, cfg_webaccess_warning_msgs[0]) else: return (1, "%s %s" % (cfg_webaccess_warning_msgs[1], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) # 3.2 if optional == 'yes': if verbose: print ' - action with optional arguments' connection = run_sql("""SELECT * FROM accROLE_accACTION_accARGUMENT WHERE id_accROLE IN (%s) AND id_accACTION = %s AND id_accARGUMENT = -1 AND argumentlistid = -1 """ % (str_roles, id_action)) if connection and 1: return (0, cfg_webaccess_warning_msgs[0]) else: return (1, "%s %s" % (cfg_webaccess_warning_msgs[1], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) # none of the zeroargs tests succeded if verbose: print ' - not authorization without arguments' return (5, "%s %s" % (cfg_webaccess_warning_msgs[5], (called_from and "%s" % (cfg_webaccess_msgs[1] or "")))) # TASK 4: create list of keyword and values that satisfy part of the authentication and create or-string if verbose: print 'task 4 - create keyword=value pairs' # create dictionary with default values and replace entries from input arguments defdict = {} for key in defkeys: try: defdict[key] = arguments[key] except KeyError: return (5, "%s %s" % (cfg_webaccess_warning_msgs[5], (called_from and "%s" % (cfg_webaccess_msgs[1] or "")))) # all keywords must be present # except KeyError: defdict[key] = 'x' # default value, this is not in use... # create or-string from arguments str_args = '' for key in defkeys: if str_args: str_args += ' OR ' str_args += """(arg.keyword = '%s' AND arg.value = '%s')""" % (key, defdict[key]) # TASK 5: find all the table entries that partially authorize the action in question if verbose: print 'task 5 - find table entries that are part of the result' query4 = """SELECT DISTINCT raa.id_accROLE, raa.id_accACTION, raa.argumentlistid, raa.id_accARGUMENT, arg.keyword, arg.value FROM accROLE_accACTION_accARGUMENT raa, accARGUMENT arg WHERE raa.id_accACTION = %s AND raa.id_accROLE IN (%s) AND (%s) AND raa.id_accARGUMENT = arg.id """ % (id_action, str_roles, str_args) try: res4 = run_sql(query4) except ProgrammingError: return (3, "%s %s" % (cfg_webaccess_warning_msgs[3], (called_from and "%s" % (cfg_webaccess_msgs[1] or "")))) if not res4: return (1, "%s %s" % (cfg_webaccess_warning_msgs[1], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) # no entries at all res5 = [] for res in res4: res5.append(res) res5.sort() # USER AUTHENTICATED TO PERFORM ACTION WITH ONE ARGUMENT if len(defdict) == 1: return (0, cfg_webaccess_warning_msgs[0]) # CHECK WITH MORE THAN 1 ARGUMENT # TASK 6: run through the result and try to satisfy authentication if verbose: print 'task 6 - combine results and try to satisfy' cur_role = cur_action = cur_arglistid = 0 booldict = {} for key in defkeys: booldict[key] = 0 # run through the results for (role, action, arglistid, arg, keyword, val) in res5 + [(-1, -1, -1, -1, -1, -1)]: # not the same role or argumentlist (authorization group), i.e. check if thing are satisfied # if cur_arglistid != arglistid or cur_role != role or cur_action != action: if (cur_arglistid, cur_role, cur_action) != (arglistid, role, action): if verbose: print ' : checking new combination', # test if all keywords are satisfied for value in booldict.values(): if not value: break else: if verbose: print '-> found satisfying combination' return (0, cfg_webaccess_warning_msgs[0]) # USER AUTHENTICATED TO PERFORM ACTION if verbose: print '-> not this one' # assign the values for the current tuple from the query cur_arglistid, cur_role, cur_action = arglistid, role, action for key in booldict.keys(): booldict[key] = 0 # set keyword qualified for the action, (whatever result of the test) booldict[keyword] = 1 if verbose: print 'finished' # authentication failed return (4, "%s %s" % (cfg_webaccess_warning_msgs[4], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) </protect> diff --git a/modules/webaccess/lib/access_control_engine.py.wml b/modules/webaccess/lib/access_control_engine.py.wml index d4e3a761c..526937f37 100644 --- a/modules/webaccess/lib/access_control_engine.py.wml +++ b/modules/webaccess/lib/access_control_engine.py.wml @@ -1,220 +1,225 @@ ## $Id$ ## CDSware Access Control Engine in mod_python. ## This file is part of the CERN Document Server Software (CDSware). ## Copyright (C) 2002 CERN. ## ## The CDSware is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## ## The CDSware is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with CDSware; if not, write to the Free Software Foundation, Inc., ## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. """CDSware Access Control Engine in mod_python.""" <protect> ## okay, rest of the Python code goes below ####### __version__ = "$Id$" ## import interesting modules: from config import * from dbquery import run_sql from MySQLdb import ProgrammingError from access_control_config import SUPERADMINROLE, cfg_webaccess_warning_msgs, cfg_webaccess_msgs called_from = 1 #1=web,0=cli try: import _apache except ImportError, e: called_from = 0 ## access controle engine function def acc_authorize_action(id_user, name_action, verbose=0, **arguments): """Check if user is allowed to perform action with given list of arguments. Return (0, message) if authentication succeeds, (error code, error message) if it fails. The arguments are as follows: id_user - id of the user in the database name_action - the name of the action arguments - dictionary with keyword=value pairs created automatically by python on the extra arguments. these depend on the given action. """ # TASK 0: find id and allowedkeywords of action if verbose: print 'task 0 - get action info' query1 = """select a.id, a.allowedkeywords, a.optional from accACTION a where a.name = '%s'""" % (name_action) try: id_action, aallowedkeywords, optional = run_sql(query1)[0] except (ProgrammingError, IndexError): return (3, "%s %s" % (cfg_webaccess_warning_msgs[3] % name_action, (called_from and cfg_webaccess_msgs[1] or ""))) defkeys = aallowedkeywords.split(',') for key in arguments.keys(): if key not in defkeys: return (8, "%s %s" % (cfg_webaccess_warning_msgs[8], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) #incorrect arguments? # ------------------------------------------- # TASK 1: check if user is a superadmin # we know the action exists. no connection with role is necessary # passed arguments must have allowed keywords # no check to see if the argument exists if verbose: print 'task 1 - is user %s' % (SUPERADMINROLE, ) if run_sql("""SELECT * FROM accROLE r LEFT JOIN user_accROLE ur ON r.id = ur.id_accROLE WHERE r.name = '%s' AND ur.id_user = '%s' """ % (SUPERADMINROLE, id_user)): return (0, cfg_webaccess_warning_msgs[0]) # ------------------------------------------ - # TASK 2: find all the user's roles and create or-string - if verbose: print 'task 2 - find userroles' - - query2 = """SELECT ur.id_accROLE FROM user_accROLE ur WHERE ur.id_user=%s ORDER BY ur.id_accROLE """ % id_user - try: res2 = run_sql(query2) + # TASK 2: check if user exists and find all the user's roles and create or-string + if verbose: print 'task 2 - find user and userroles' + + try: + query2 = """SELECT * from user where id=%s""" % id_user + res2 = run_sql(query2) + if not res2: + raise Exception + query2 = """SELECT ur.id_accROLE FROM user_accROLE ur WHERE ur.id_user=%s ORDER BY ur.id_accROLE """ % id_user + res2 = run_sql(query2) except Exception: return (6, "%s %s" % (cfg_webaccess_warning_msgs[6], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) if not res2: return (2, "%s %s" % (cfg_webaccess_warning_msgs[2], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) #user has no roles # ------------------------------------------- # create role string (add default value? roles='(raa.id_accROLE='def' or ') str_roles = '' for (role, ) in res2: if str_roles: str_roles += ',' str_roles += '%s' % (role, ) # TASK 3: authorizations with no arguments given if verbose: print 'task 3 - checks with no arguments' if not arguments: # 3.1 if optional == 'no': if verbose: print ' - action with zero arguments' connection = run_sql("""SELECT * FROM accROLE_accACTION_accARGUMENT WHERE id_accROLE IN (%s) AND id_accACTION = %s AND argumentlistid = 0 AND id_accARGUMENT = 0 """ % (str_roles, id_action)) if connection and 1: return (0, cfg_webaccess_warning_msgs[0]) else: return (1, "%s %s" % (cfg_webaccess_warning_msgs[1], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) # 3.2 if optional == 'yes': if verbose: print ' - action with optional arguments' connection = run_sql("""SELECT * FROM accROLE_accACTION_accARGUMENT WHERE id_accROLE IN (%s) AND id_accACTION = %s AND id_accARGUMENT = -1 AND argumentlistid = -1 """ % (str_roles, id_action)) if connection and 1: return (0, cfg_webaccess_warning_msgs[0]) else: return (1, "%s %s" % (cfg_webaccess_warning_msgs[1], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) # none of the zeroargs tests succeded if verbose: print ' - not authorization without arguments' return (5, "%s %s" % (cfg_webaccess_warning_msgs[5], (called_from and "%s" % (cfg_webaccess_msgs[1] or "")))) # TASK 4: create list of keyword and values that satisfy part of the authentication and create or-string if verbose: print 'task 4 - create keyword=value pairs' # create dictionary with default values and replace entries from input arguments defdict = {} for key in defkeys: try: defdict[key] = arguments[key] except KeyError: return (5, "%s %s" % (cfg_webaccess_warning_msgs[5], (called_from and "%s" % (cfg_webaccess_msgs[1] or "")))) # all keywords must be present # except KeyError: defdict[key] = 'x' # default value, this is not in use... # create or-string from arguments str_args = '' for key in defkeys: if str_args: str_args += ' OR ' str_args += """(arg.keyword = '%s' AND arg.value = '%s')""" % (key, defdict[key]) # TASK 5: find all the table entries that partially authorize the action in question if verbose: print 'task 5 - find table entries that are part of the result' query4 = """SELECT DISTINCT raa.id_accROLE, raa.id_accACTION, raa.argumentlistid, raa.id_accARGUMENT, arg.keyword, arg.value FROM accROLE_accACTION_accARGUMENT raa, accARGUMENT arg WHERE raa.id_accACTION = %s AND raa.id_accROLE IN (%s) AND (%s) AND raa.id_accARGUMENT = arg.id """ % (id_action, str_roles, str_args) try: res4 = run_sql(query4) except ProgrammingError: return (3, "%s %s" % (cfg_webaccess_warning_msgs[3], (called_from and "%s" % (cfg_webaccess_msgs[1] or "")))) if not res4: return (1, "%s %s" % (cfg_webaccess_warning_msgs[1], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) # no entries at all res5 = [] for res in res4: res5.append(res) res5.sort() # USER AUTHENTICATED TO PERFORM ACTION WITH ONE ARGUMENT if len(defdict) == 1: return (0, cfg_webaccess_warning_msgs[0]) # CHECK WITH MORE THAN 1 ARGUMENT # TASK 6: run through the result and try to satisfy authentication if verbose: print 'task 6 - combine results and try to satisfy' cur_role = cur_action = cur_arglistid = 0 booldict = {} for key in defkeys: booldict[key] = 0 # run through the results for (role, action, arglistid, arg, keyword, val) in res5 + [(-1, -1, -1, -1, -1, -1)]: # not the same role or argumentlist (authorization group), i.e. check if thing are satisfied # if cur_arglistid != arglistid or cur_role != role or cur_action != action: if (cur_arglistid, cur_role, cur_action) != (arglistid, role, action): if verbose: print ' : checking new combination', # test if all keywords are satisfied for value in booldict.values(): if not value: break else: if verbose: print '-> found satisfying combination' return (0, cfg_webaccess_warning_msgs[0]) # USER AUTHENTICATED TO PERFORM ACTION if verbose: print '-> not this one' # assign the values for the current tuple from the query cur_arglistid, cur_role, cur_action = arglistid, role, action for key in booldict.keys(): booldict[key] = 0 # set keyword qualified for the action, (whatever result of the test) booldict[keyword] = 1 if verbose: print 'finished' # authentication failed return (4, "%s %s" % (cfg_webaccess_warning_msgs[4], (called_from and "%s %s" % (cfg_webaccess_msgs[0] % name_action[3:], cfg_webaccess_msgs[1]) or ""))) </protect>