def _filter_members(request, group, users, ldap_user_read=None): members = [] for member_dn in users: try: user = User.from_dn(member_dn, None, ldap_user_read) except udm_exceptions.noObject: MODULE.process( 'Could not open (foreign) user %r: no permissions/does not exists/not a user' % (member_dn, )) continue if not user.schools or not set(user.schools) & {group.school}: continue if request.flavor == 'class' and not user.is_teacher( ldap_user_read): continue # only display teachers elif request.flavor == 'workgroup' and not user.is_student( ldap_user_read): continue # only display students elif request.flavor == 'workgroup-admin' and not user.is_student( ldap_user_read) and not user.is_administrator( ldap_user_read) and not user.is_staff( ldap_user_read) and not user.is_teacher( ldap_user_read): continue # only display school users members.append({ 'id': user.dn, 'label': Display.user(user.get_udm_object(ldap_user_read)) }) return members
def get(self, request, ldap_user_read=None, ldap_position=None): """Returns the objects for the given IDs requests.options = [ <ID>, ... ] return: [ { ... }, ... ] """ # try to load all given projects result = [] # list of all project properties (dicts) or None if project is not valid for iproject in [util.Project.load(iid) for iid in request.options]: # make sure that project could be loaded if not iproject: result.append(None) continue # make sure that only the project owner himself (or an admin) is able # to see the content of a project if request.flavor == 'teacher' and not compare_dn(iproject.sender.dn, self.user_dn): raise UMC_Error(_('Project details are only visible to the project owner himself or an administrator.'), status=403) # prepare date and time properties for distribution/collection of project files props = iproject.dict for jjob, jsuffix in ((iproject.atJobDistribute, 'distribute'), (iproject.atJobCollect, 'collect')): MODULE.info('check job: %s' % jsuffix) if not jjob: # no job is registered -> manual job distribution/collection MODULE.info('no existing job -> manual execution') props['%sType' % jsuffix] = 'manual' continue # job is registered -> prepare date and time fields MODULE.info('job nr #%d scheduled for %s -> automatic execution' % (jjob.nr, jjob.execTime)) props['%sType' % jsuffix] = 'automatic' props['%sDate' % jsuffix] = datetime.strftime(jjob.execTime, '%Y-%m-%d') props['%sTime' % jsuffix] = datetime.strftime(jjob.execTime, '%H:%M') # adjust sender / recipients properties props['sender'] = props['sender'].username recipients = [] for recip in props['recipients']: recipients.append({ 'id': recip.dn, 'label': recip.type == util.TYPE_USER and Display.user(recip.dict) or recip.name }) props['recipients'] = recipients # append final dict to result list MODULE.info('final project dict: %s' % props) result.append(props) self.finished(request.id, result)
def query(self, request, ldap_user_read=None, ldap_position=None): """Searches for students""" klass = request.options.get('class') if klass in (None, 'None'): klass = None result = [{ 'id': usr.dn, 'name': Display.user(usr), 'passwordexpiry': usr.get('passwordexpiry', '') } for usr in self._users(ldap_user_read, request.options['school'], group=klass, user_type=request.flavor, pattern=request.options.get('pattern', ''))] self.finished(request.id, result)
def json(self): return { 'id': self.fullfilename, 'username': self.username, 'user': Display.user(self.owner), 'printjob': self.name, 'filename': self.filename, 'date': (self.ctime.year, self.ctime.month, self.ctime.day, self.ctime.hour, self.ctime.minute, self.ctime.second), 'pages': self.metadata.get('pages') }
def update(self, request, ldap_user_read=None): """Returns an update for the computers in the selected room. Just attributes that have changed since the last call will be included in the result """ if not self._italc.school or not self._italc.room: raise UMC_Error('no room selected') computers = [ computer.dict for computer in self._italc.values() if computer.hasChanged ] info = _readRoomInfo(self._italc.roomDN) result = { 'computers': computers, 'room_info': info, 'locked': info.get('user', self.user_dn) != self.user_dn, 'user': self.user_dn, } if result['locked'] and 'pid' in info: result['user'] = info['user'] # somebody else acquired the room, the room is locked try: # open the corresponding UDM object to get a displayable user name result['user'] = Display.user( User.from_dn( result['user'], None, ldap_user_read).get_udm_object(ldap_user_read)) except udm_exceptions.base as exc: # could not oben the LDAP object, show the DN MODULE.warn('Cannot open LDAP information for user %r: %s' % (result['user'], exc)) # settings info if self._ruleEndAt is not None: diff = self._positiveTimeDiff() if diff is not None: result['settingEndsIn'] = diff.seconds / 60 MODULE.info('Update: result: %s' % str(result)) self.finished(request.id, result)
def users(self, request, ldap_user_read=None, ldap_position=None): # parse group parameter group = request.options.get('group') user_type = None if not group or group == 'None': group = None elif group.lower() in ('teacher', 'student'): user_type = group.lower() group = None result = [{ 'id': i.dn, 'label': Display.user(i) } for i in self._users(ldap_user_read, request.options['school'], group=group, user_type=user_type, pattern=request.options['pattern'])] self.finished(request.id, result)
def distribute(self, request): # update the sender information of the selected projects result = [] for iid in request.options: MODULE.info('Distribute project: %s' % iid) try: # make sure that project could be loaded iproject = util.Project.load(iid) if not iproject: raise IOError(_('Project "%s" could not be loaded') % iid) # make sure that only the project owner himself (or an admin) is able # to distribute a project if request.flavor == 'teacher' and not compare_dn(iproject.sender.dn, self.user_dn): raise ValueError(_('Only the owner himself or an administrator may distribute a project.')) # project was loaded successfully... try to distribute it usersFailed = [] iproject.distribute(usersFailed) # raise an error in case distribution failed for some users if usersFailed: MODULE.info('Failed processing the following users: %s' % usersFailed) usersStr = ', '.join([Display.user(i) for i in usersFailed]) raise IOError(_('The project could not distributed to the following users: %s') % usersStr) # save result result.append(dict( name=iid, success=True )) except (ValueError, IOError) as exc: result.append(dict( name=iid, success=False, details=str(exc) )) # return the results self.finished(request.id, result)
def rooms(self, request, ldap_user_read=None): """Returns a list of all available rooms""" rooms = [] try: all_rooms = ComputerRoom.get_all(ldap_user_read, request.options['school']) except udm_exceptions.noObject: all_rooms = [] for room in all_rooms: room_info = _readRoomInfo(room.dn) user_dn = room_info.get('user') locked = user_dn and not compare_dn(user_dn, self.user_dn) and ( 'pid' in room_info or 'exam' in room_info) if locked: try: # open the corresponding UDM object to get a displayable user name user_dn = Display.user( User.from_dn( user_dn, None, ldap_user_read).get_udm_object(ldap_user_read)) except udm_exceptions.base as exc: MODULE.warn( 'Cannot open LDAP information for user %r: %s' % (user_dn, exc)) rooms.append({ 'id': room.dn, 'label': room.get_relative_name(), 'user': user_dn, 'locked': locked, 'exam': room_info.get('exam'), 'examDescription': room_info.get('examDescription'), 'examEndTime': room_info.get('examEndTime'), }) self.finished(request.id, rooms)
def put(self, request, ldap_machine_write=None, ldap_user_read=None, ldap_position=None): """Returns the objects for the given IDs requests.options = [ { object : ..., options : ... }, ... ] return: True|<error message> """ if request.flavor == 'teacher': request.options = request.options[0]['object'] return self.add_teacher_to_classes(request) klass = get_group_class(request) for group_from_umc in request.options: group_from_umc = group_from_umc['object'] group_from_umc_dn = group_from_umc['$dn$'] break try: group_from_ldap = klass.from_dn(group_from_umc_dn, None, ldap_machine_write) except udm_exceptions.noObject: raise UMC_Error('unknown group object') old_members = self._filter_members(request, group_from_ldap, group_from_ldap.users, ldap_user_read) removed_members = set(o['id'] for o in old_members) - set( group_from_umc['members']) MODULE.info('Modifying group "%s" with members: %s' % (group_from_ldap.dn, group_from_ldap.users)) MODULE.info('New members: %s' % group_from_umc['members']) MODULE.info('Removed members: %s' % (removed_members, )) if request.flavor == 'workgroup-admin': # do not allow groups to be renamed in order to avoid conflicts with shares # grp.name = '%(school)s-%(name)s' % group group_from_ldap.description = group_from_umc['description'] # Workgroup admin view → update teachers, admins, students, (staff) # Class view → update only the group's teachers (keep all non teachers) # Workgroup teacher view → update only the group's students users = [] # keep specific users from the group for userdn in group_from_ldap.users: try: user = User.from_dn(userdn, None, ldap_machine_write) except udm_exceptions.noObject: # no permissions/is not a user/does not exists → keep the old value users.append(userdn) continue if not user.schools or not set(user.schools) & set( [group_from_ldap.school]): users.append(userdn) continue if (request.flavor == 'class' and not user.is_teacher(ldap_machine_write)) or ( request.flavor == 'workgroup' and not user.is_student(ldap_machine_write) ) or request.flavor == 'workgroup-admin': users.append(userdn) # add only certain users to the group for userdn in group_from_umc['members']: try: user = User.from_dn(userdn, None, ldap_machine_write) except udm_exceptions.noObject as exc: MODULE.error('Not adding not existing user %r to group: %r.' % (userdn, exc)) continue if not user.schools or not set(user.schools) & set( [group_from_ldap.school]): raise UMC_Error( _('User %s does not belong to school %r.') % (Display.user(user.get_udm_object(ldap_machine_write)), group_from_ldap.school)) if request.flavor == 'workgroup-admin' and not user.is_student( ldap_machine_write) and not user.is_administrator( ldap_machine_write) and not user.is_staff( ldap_machine_write) and not user.is_teacher( ldap_machine_write): raise UMC_Error( _('User %s does not belong to school %r.') % (Display.user(user.get_udm_object(ldap_machine_write)), group_from_ldap.school)) if request.flavor == 'class' and not user.is_teacher( ldap_machine_write): raise UMC_Error( _('User %s is not a teacher.') % (Display.user(user.get_udm_object(ldap_machine_write)), )) if request.flavor == 'workgroup' and not user.is_student( ldap_machine_write): raise UMC_Error( _('User %s is not a student.') % (Display.user(user.get_udm_object(ldap_machine_write)), )) users.append(user.dn) group_from_ldap.users = list(set(users) - removed_members) try: success = group_from_ldap.modify(ldap_machine_write) MODULE.info('Modified, group has now members: %s' % (group_from_ldap.users, )) except udm_exceptions.base as exc: MODULE.process('An error occurred while modifying "%s": %s' % (group_from_umc['$dn$'], exc.message)) raise UMC_Error(_('Failed to modify group (%s).') % exc.message) self.finished(request.id, success)
def _save(self, iprops, doUpdate=True, ldap_user_read=None, ldap_position=None): # try to open the UDM user object of the current user sender = self._get_sender() try: # remove keys that may not be set from outside for k in ('atJobNumCollect', 'atJobNumDistribute'): iprops.pop(k, None) # transform filenames into bytestrings iprops['files'] = [f.encode('UTF-8') for f in iprops.get('files', [])] # load the project or create a new one project = None orgProject = None if doUpdate: # try to load the given project orgProject = util.Project.load(iprops.get('name', '')) if not orgProject: raise UMC_Error(_('The specified project does not exist: %s') % iprops['name']) # create a new project with the updated values project = util.Project(orgProject.dict) project.update(iprops) else: # create a new project project = util.Project(iprops) # make sure that the project owner himself is modifying the project if doUpdate and not compare_dn(project.sender.dn, self.user_dn): raise UMC_Error(_('The project can only be modified by the owner himself')) # handle time settings for distribution/collection of project files for jsuffix, jprop, jname in (('distribute', 'starttime', _('Project distribution')), ('collect', 'deadline', _('Project collection'))): if '%sType' % jsuffix in iprops: # check the distribution/collection type: manual/automat jtype = (iprops['%sType' % jsuffix]).lower() if jtype == 'automatic': try: # try to parse the given time parameters strtime = '%s %s' % (iprops['%sDate' % jsuffix], iprops['%sTime' % jsuffix]) jdate = datetime.strptime(strtime, '%Y-%m-%d %H:%M') setattr(project, jprop, jdate) except ValueError: raise UMC_Error(_('Could not set date for: %s') % jname) # make sure the execution time lies sufficiently in the future if getattr(project, jprop) - datetime.now() < timedelta(minutes=1): raise UMC_Error(_('The specified time needs to lie in the future for: %s') % jname) else: # manual distribution/collection setattr(project, jprop, None) if project.starttime and project.deadline: # make sure distributing happens before collecting if project.deadline - project.starttime < timedelta(minutes=3): raise UMC_Error(_('Distributing the data needs to happen sufficiently long enough before collecting them')) if 'recipients' in iprops: # lookup the users in LDAP and save them to the project project.recipients = [util.openRecipients(idn, ldap_user_read) for idn in iprops.get('recipients', [])] project.recipients = [x for x in project.recipients if x] MODULE.info('recipients: %s' % (project.recipients,)) if not doUpdate: # set the sender (i.e., owner) of the project project.sender = sender # initiate project and validate its values project.validate() # make sure that there is no other project with the same directory name # if we add new projects if not doUpdate and project.isNameInUse(): MODULE.error('The project name is already in use: %s' % (project.name)) raise UMC_Error(_('The specified project directory name "%s" is already in use by a different project.') % (project.name)) # try to save project to disk project.save() # move new files into project directory if self._tmpDir: for ifile in project.files: isrc = os.path.join(self._tmpDir, ifile) itarget = os.path.join(project.cachedir, ifile) if os.path.exists(isrc): # mv file to cachedir shutil.move(isrc, itarget) os.chown(itarget, 0, 0) # remove files that have been marked for removal if doUpdate: for ifile in set(orgProject.files) - set(project.files): itarget = os.path.join(project.cachedir, ifile) try: os.remove(itarget) except OSError: pass # re-distribute the project in case it has already been distributed if doUpdate and project.isDistributed: usersFailed = [] project.distribute(usersFailed) if usersFailed: # not all files could be distributed MODULE.info('Failed processing the following users: %s' % usersFailed) usersStr = ', '.join([Display.user(i) for i in usersFailed]) raise UMC_Error(_('The project could not distributed to the following users: %s') % usersStr) except (IOError, OSError, UMC_Error): # TODO: catch only UMC_Error etype, exc, etraceback = sys.exc_info() # data not valid... create error info MODULE.info('data for project "%s" is not valid: %s' % (iprops.get('name'), exc)) if not doUpdate: # remove eventually created project file and cache dir for ipath in (project.projectfile, project.cachedir): if os.path.basename(ipath) not in os.listdir(util.DISTRIBUTION_DATA_PATH): # no file / directory has been created yet continue try: MODULE.info('cleaning up... removing: %s' % ipath) shutil.rmtree(ipath) except (IOError, OSError): pass raise UMC_Error, exc, etraceback self._cleanTmpDir() return {'success': True, 'name': iprops.get('name')}