Source code for roster_core.user

#!/usr/bin/python

# Copyright (c) 2009, Purdue University
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or
# other materials provided with the distribution.
# 
# Neither the name of the Purdue University nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""Classes pertaining to users and authorization for Roster.

Authorization for specific functions and for specific domain/ip range blocks
is handled in this module.
"""

__copyright__ = 'Copyright (C) 2009, Purdue University'
__license__ = 'BSD'
__version__ = '#TRUNK#'


import IPy

import constants
import errors
import helpers_lib

[docs]class User(object): """Representation of a user, with basic manipulation methods. Note that is it not necessary to authenticate a user to construct this class. This class is mainly responsible for authorization. """ def __init__(self, user_name, db_instance, log_instance): """ Inputs: user_name: user's login db_instance: dbAccess instance to use for user verification Raises: InvalidInputError: No such user. """ self.user_name = user_name self.db_instance = db_instance self.log_instance = log_instance self.zone_origin_cache = {} # pull a pile of authentication info from the database here self.user_perms = self.db_instance.GetUserAuthorizationInfo(user_name) if( not self.user_perms.has_key('user_name') ): raise errors.InvalidInputError("No such user: %s" % user_name) # More DB crud self.groups = self.user_perms['groups'] self.forward_zones = self.user_perms['forward_zones'] self.reverse_ranges = self.user_perms['reverse_ranges'] self.user_access_level = ual = self.user_perms['user_access_level'] # Pull zone origins for cache self.db_instance.StartTransaction() try: for zone in self.forward_zones: self.zone_origin_cache[ zone['zone_name']] = self.db_instance.GetZoneOrigin( zone['zone_name'], None) finally: self.db_instance.EndTransaction() # Build a hash of methods, using the supported_method hash self.abilities = {} for method in constants.SUPPORTED_METHODS.keys(): if( constants.SUPPORTED_METHODS[method]['access_level'] <= ual ): self.abilities[method] = constants.SUPPORTED_METHODS[method]
[docs] def Authorize(self, method, record_data=None, current_transaction=False): """Check to see if the user is authorized to run the given operation. Inputs: method: what the user's trying to do record_data: dictionary of target, zone_name, view_name, record_type, and record_args_dict for the record that is being modified. {'target': 'test_target', 'zone_name': 'test_zone', 'view_name': 'test_view', 'record_type': 'a', 'record_args_dict' : { u'assignment_ip' : '192.168.1.1' } } current_transaction: bool of if this function is run from inside a transaction in the db_access class Raises: MaintenanceError: Roster is currently under maintenance. MissingDataTypeError: Incomplete record data provided for access method. AuthorizationError: Authorization failure. """ function_name, current_args = helpers_lib.GetFunctionNameAndArgs() if( not current_transaction ): self.db_instance.StartTransaction() try: maintenance_mode = self.db_instance.CheckMaintenanceFlag() if( record_data and record_data.has_key('zone_name') and not self.zone_origin_cache.has_key(record_data['zone_name']) ): self.zone_origin_cache[ record_data['zone_name']] = self.db_instance.GetZoneOrigin( record_data['zone_name'], record_data['view_name']) finally: if( not current_transaction ): self.db_instance.EndTransaction() if( maintenance_mode and self.user_perms['user_access_level'] != constants.ACCESS_LEVELS['dns_admin'] ): raise errors.MaintenanceError('Roster is currently under maintenance.') if( record_data is not None and record_data.has_key('zone_name') ): target_string = ' with %s on %s of type %s' % (record_data['target'], record_data['zone_name'], record_data['record_type']) user_group_perms = [] record_target = record_data['target'] if( record_target == u'@' ): ip_address = helpers_lib.UnReverseIP( self.zone_origin_cache[record_data['zone_name']]) else: ip_address = helpers_lib.UnReverseIP('%s.%s' % ( record_target, self.zone_origin_cache[record_data['zone_name']])) #Looking for permissions in the forward zones for zone in self.user_perms['forward_zones']: if( zone['zone_name'] == record_data['zone_name'] ): user_group_perms.append(zone['group_permission']) #If we haven't found any, look in the reverse ranges if( user_group_perms == [] ): validation_instance = self.db_instance.data_validation_instance if( validation_instance.isIPv4IPAddress(ip_address) or validation_instance.isIPv6IPAddress(ip_address) ): for cidr in self.user_perms['reverse_ranges']: if( IPy.IP(cidr['cidr_block']).overlaps(ip_address) ): user_group_perms.append(cidr['group_permission']) else: target_string = '' auth_fail_string = ('User %s is not allowed to use %s%s' % (self.user_name, method, target_string)) #authorizing method if( self.abilities.has_key(method) ): method_hash = self.abilities[method] if( int(self.user_access_level) >= constants.ACCESS_LEVELS['dns_admin'] ): return if( method_hash['check'] ): # Secondary check - ensure the target is in a range delegated to # the user if( record_data is None ): raise errors.MissingDataTypeError( 'No record data provided for access method ' '%s' % method) elif( not record_data.has_key('zone_name') or record_data['zone_name'] is None or not record_data.has_key('view_name') or record_data['view_name'] is None or not record_data.has_key('target') or record_data['target'] is None or not record_data.has_key('record_type') or record_data['record_type'] is None or not record_data.has_key('record_args_dict') or record_data['record_args_dict'] is None ): raise errors.MissingDataTypeError( 'Incomplete record data provided for access ' 'method %s' % method) elif( record_data['record_type'] not in user_group_perms and int(self.user_access_level) <= constants.ACCESS_LEVELS[ 'unlocked_user'] ): raise errors.AuthorizationError(auth_fail_string) if( int(self.user_access_level) < constants.ACCESS_LEVELS['unlocked_user'] ): # if a or aaaa if( record_data['record_args_dict'].has_key(u'assignment_ip') ): ip = IPy.IP(record_data['record_args_dict'][u'assignment_ip']) for reverse_range in self.reverse_ranges: if( IPy.IP(reverse_range['cidr_block']).overlaps(ip) ): break else: raise errors.AuthorizationError(auth_fail_string) # if cname, mx, ns, or ptr elif( record_data['record_args_dict'].has_key(u'assignment_host') or record_data['record_args_dict'].has_key(u'mail_server') or record_data['record_args_dict'].has_key(u'name_server') ): hostname = None if( record_data['record_args_dict'].has_key(u'assignment_host') ): hostname = record_data['record_args_dict'][u'assignment_host'] elif( record_data['record_args_dict'].has_key(u'mail_server') ): hostname = record_data['record_args_dict'][u'mail_server'] elif( record_data['record_args_dict'].has_key(u'name_server') ): hostname = record_data['record_args_dict'][u'name_server'] smallest_zone = hostname found = False while( smallest_zone ): for domain in self.zone_origin_cache: if( self.zone_origin_cache[domain] ): domain = self.zone_origin_cache[domain] if( smallest_zone == domain ): found = True if( not found ): try: smallest_zone = smallest_zone.split('.', 1)[1] except IndexError: raise errors.AuthorizationError(auth_fail_string) else: break if( not found ): raise errors.AuthorizationError(auth_fail_string) for zone in self.forward_zones: if( self.zone_origin_cache.has_key(zone['zone_name']) ): if( smallest_zone == self.zone_origin_cache[ zone['zone_name']] ): break else: raise errors.AuthorizationError(auth_fail_string) for forward_zone in self.forward_zones: if( record_data['zone_name'] == forward_zone['zone_name'] ): return # Can't find it in forward zones, maybe it's a reverse try: ip = IPy.IP(ip_address) # Good, we have an IP. See if we hit any delegated ranges. for reverse_range in self.reverse_ranges: if( IPy.IP(reverse_range['cidr_block']).overlaps(ip) ): return # fail to find a matching IP range with appropriate perms self.log_instance.LogAction(self.user_name, function_name, current_args, False, current_transaction) raise errors.AuthorizationError(auth_fail_string) except ValueError: # fail to find a matching zone with appropriate perms self.log_instance.LogAction(self.user_name, function_name, current_args, False, current_transaction) raise errors.AuthorizationError(auth_fail_string) else: return else: # fail to find a matching method self.log_instance.LogAction(self.user_name, function_name, current_args, False, current_transaction) raise errors.AuthorizationError(auth_fail_string)
[docs] def GetUserName(self): """Return user name for current session. Outputs: string: user name """ return self.user_perms['user_name']
[docs] def GetPermissions(self): """Return permissions and groups for user. Outputs: dictionary of permissions example: {'user_access_level': '2', 'user_name': 'shuey', 'forward_zones': [ {'zone_name': 'cs.university.edu', 'group_permission': 'rw'}, {'zone_name': 'eas.university.edu', 'group_permission': 'r'}, {'zone_name': 'bio.university.edu', 'group_permission': 'rw'}], 'groups': ['cs', 'bio'], 'reverse_ranges': [ {'cidr_block': '192.168.0.0/24', 'group_permission': 'rw'}, {'cidr_block': '192.168.0.0/24', 'group_permission': 'r'}, {'cidr_block': '192.168.1.0/24', 'group_permission': 'rw'}]} """ return self.user_perms # vi: set ai aw sw=2: