Beispiel #1
0
class Instance(SchoolBaseModule):
    @sanitize(
        **{
            'school': SchoolSanitizer(required=True),
            'class': StringSanitizer(required=True),  # allow_none=True
            'pattern': StringSanitizer(required=True),
        })
    @LDAP_Connection()
    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)

    @sanitize(
        userDN=StringSanitizer(required=True),
        newPassword=StringSanitizer(required=True, minimum=1),
        nextLogin=BooleanSanitizer(default=True),
    )
    @LDAP_Connection(USER_WRITE)
    def password_reset(self, request, ldap_user_write=None):
        '''Reset the password of the user'''
        userdn = request.options['userDN']
        pwdChangeNextLogin = request.options['nextLogin']
        newPassword = request.options['newPassword']

        try:
            user = User.from_dn(
                userdn, None, ldap_user_write).get_udm_object(ldap_user_write)
            user['password'] = newPassword
            user['overridePWHistory'] = '1'
            user['locked'] = 'none'
            user['pwdChangeNextLogin'] = '******' if pwdChangeNextLogin else '0'
            user.modify()
            self.finished(request.id, True)
        except udm_exceptions.permissionDenied as exc:
            MODULE.process('dn=%r' % (userdn, ))
            MODULE.process('exception=%s' % (type(exc), ))
            raise UMC_Error(_('permission denied'))
        except udm_exceptions.base as exc:
            MODULE.process('dn=%r' % (userdn, ))
            MODULE.process('exception=%s' % (type(exc), ))
            MODULE.process('exception=%s' % (exc.message, ))
            raise UMC_Error('%s' % (get_exception_msg(exc)))
Beispiel #2
0
class Instance(SchoolBaseModule):
    ATJOB_KEY = 'UMC-computerroom'

    def init(self):
        SchoolBaseModule.init(self)
        ComputerSanitizer.instance = self
        self._italc = ITALC_Manager()
        self._random = Random()
        self._random.seed()
        self._lessons = SchoolLessons()
        self._ruleEndAt = None
        self._load_plugins()

    def _load_plugins(self):
        self._plugins = {}
        for module in os.listdir(
                os.path.join(os.path.dirname(__file__), 'plugins')):
            if module.endswith('.py'):
                try:
                    module = importlib.import_module(
                        'univention.management.console.modules.computerroom.plugins.%s'
                        % (module[:-3], ))
                except ImportError:
                    MODULE.error(traceback.format_exc())
                for name, plugin in inspect.getmembers(module,
                                                       inspect.isclass):
                    MODULE.info('Loading plugin %r from module %r' % (
                        plugin,
                        module,
                    ))
                    if not name.startswith(
                            '_') and plugin is not Plugin and issubclass(
                                plugin, Plugin):
                        try:
                            plugin = plugin(self, self._italc)
                        except Exception:
                            MODULE.error(traceback.format_exc())
                        else:
                            self._plugins[plugin.name] = plugin

    def destroy(self):
        '''Remove lock file when UMC module exists'''
        MODULE.info('Cleaning up')
        if self._italc.room:
            # do not remove lock file during exam mode
            info = _readRoomInfo(self._italc.roomDN)
            MODULE.info('room info: %s' % info)
            if info and not info.get('exam'):
                MODULE.info('Removing lock file for room %s (%s)' %
                            (self._italc.room, self._italc.roomDN))
                _freeRoom(self._italc.roomDN, self.user_dn)
        _exit(0)

    def lessons(self, request):
        """Returns a list of school lessons. Lessons in the past are filtered out"""
        current = self._lessons.current
        if current is None:
            current = self._lessons.previous

        if current:
            lessons = [
                x for x in self._lessons.lessons if x.begin >= current.begin
            ]
        else:
            lessons = self._lessons.lessons
        self.finished(request.id, [x.name for x in lessons])

    def internetrules(self, request):
        """Returns a list of available internet rules"""
        self.finished(request.id, [x.name for x in internetrules.list()])

    @sanitize(room=DNSanitizer(required=True))
    @LDAP_Connection()
    def room_acquire(self, request, ldap_user_read=None):
        """Acquires the specified computerroom"""
        roomDN = request.options['room']

        success = True
        message = 'OK'

        # match the corresponding school OU
        try:
            room = ComputerRoom.from_dn(roomDN, None, ldap_user_read)
            school = room.school
        except udm_exceptions.noObject:
            success = False
            message = 'UNKNOWN_ROOM'
        else:
            # set room and school
            if self._italc.school != school:
                self._italc.school = school
            if self._italc.room != roomDN:
                try:
                    self._italc.room = roomDN
                except ITALC_Error:
                    success = False
                    message = 'EMPTY_ROOM'

        # update the room info file
        if success:
            _updateRoomInfo(roomDN, user=self.user_dn)
            if not compare_dn(_getRoomOwner(roomDN), self.user_dn):
                success = False
                message = 'ALREADY_LOCKED'

        info = dict()
        if success:
            info = _readRoomInfo(roomDN)
        self.finished(
            request.id,
            dict(success=success,
                 message=message,
                 info=dict(
                     exam=info.get('exam'),
                     examDescription=info.get('examDescription'),
                     examEndTime=info.get('examEndTime'),
                     room=info.get('room'),
                     user=info.get('user'),
                 )))

    @sanitize(school=SchoolSanitizer(required=True))
    @LDAP_Connection()
    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)

    @sanitize(ipaddress=ListSanitizer(required=True,
                                      sanitizer=IPAddressSanitizer(),
                                      min_elements=1,
                                      max_elements=10))
    @LDAP_Connection()
    def guess_room(self, request, ldap_user_read=None):
        ipaddress = request.options['ipaddress']
        host_filter = self._get_host_filter(ipaddress)
        computers = ldap_user_read.searchDn(host_filter)
        if computers:
            room_filter = self._get_room_filter(computers)
            for school in School.get_all(ldap_user_read):
                school = school.name
                for room in ComputerRoom.get_all(ldap_user_read, school,
                                                 room_filter):
                    self.finished(request.id, dict(school=school,
                                                   room=room.dn))
                    return
        self.finished(request.id, dict(school=None, room=None))

    def _get_room_filter(self, computers):
        return '(|(%s))' % ')('.join(
            filter_format('uniqueMember=%s', (computer, ))
            for computer in computers)

    def _get_host_filter(self, ipaddresses):
        records = {4: 'aRecord=%s', 6: 'aAAARecord=%s'}
        return '(|(%s))' % ')('.join(
            filter_format(records[ipaddress.version], (ipaddress.exploded, ))
            for ipaddress in ipaddresses)

    def _checkRoomAccess(self):
        if not self._italc.room:
            return  # no room has been selected so far

        # make sure that we run the current room session
        userDN = _getRoomOwner(self._italc.roomDN)
        if userDN and not compare_dn(userDN, self.user_dn):
            raise UMC_Error(
                _('A different user is already running a computer room session.'
                  ))

    @LDAP_Connection()
    def query(self, request, ldap_user_read=None):
        """Searches for entries. This is not allowed if the room could not be acquired."""

        if not self._italc.school or not self._italc.room:
            raise UMC_Error('no room selected')

        if request.options.get('reload', False):
            self._italc.room = self._italc.room  # believe me that makes sense :)

        result = [computer.dict for computer in self._italc.values()]
        result.sort(key=lambda c: c['id'])

        MODULE.info('computerroom.query: result: %s' % (result, ))
        self.finished(request.id, result)

    @LDAP_Connection()
    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 _positiveTimeDiff(self):
        now = datetime.datetime.now()
        end = datetime.datetime.now()
        end = end.replace(hour=self._ruleEndAt.hour,
                          minute=self._ruleEndAt.minute)
        if now > end:
            return None
        return end - now

    @check_room_access
    @sanitize(
        computer=ComputerSanitizer(required=True),
        device=ChoicesSanitizer(['screen', 'input'], required=True),
        lock=BooleanSanitizer(required=True),
    )
    @simple_response
    def lock(self, computer, device, lock):
        """Lock or Unlock the screen or input of a specific computer"""

        MODULE.warn('Locking device %s' % (device, ))
        if device == 'screen':
            computer.lockScreen(lock)
        else:
            computer.lockInput(lock)

    @allow_get_request
    @check_room_access
    @sanitize(
        computer=ComputerSanitizer(required=True), )
    @prevent_ucc
    def screenshot(self, request):
        """Returns a JPEG image containing a screenshot of the given computer."""

        computer = request.options['computer']
        tmpfile = computer.screenshot
        if computer.hide_screenshot:
            filename = FN_SCREENSHOT_DENIED
        elif tmpfile is None:
            filename = FN_SCREENSHOT_NOTREADY
        else:
            filename = tmpfile.name

        MODULE.info('screenshot(%s): hide screenshot = %r' %
                    (computer.name, computer.hide_screenshot))
        try:
            with open(filename, 'rb') as fd:
                response = fd.read()
        except EnvironmentError as exc:
            MODULE.error('Unable to load screenshot file %r: %s' %
                         (filename, exc))
        try:
            if tmpfile:
                os.unlink(tmpfile.name)
        except EnvironmentError as exc:
            MODULE.error('Unable to remove temporary screenshot file %r: %s' %
                         (tmpfile.name, exc))

        self.finished(request.id, response, mimetype='image/jpeg')

    @check_room_access
    @sanitize(
        computer=ComputerSanitizer(required=True), )
    def vnc(self, request):
        """Returns a ultraVNC file for the given computer."""

        # check whether VNC is enabled
        if ucr.is_false('ucsschool/umc/computerroom/ultravnc/enabled', True):
            raise UMC_Error('VNC is disabled')

        try:
            with open('/usr/share/ucs-school-umc-computerroom/ultravnc.vnc',
                      'rb') as fd:
                content = fd.read()
        except (IOError, OSError):
            raise UMC_Error('VNC template file does not exists')

        port = ucr.get('ucsschool/umc/computerroom/vnc/port', '11100')
        hostname = request.options['computer'].ipAddress

        response = content.replace('@%@HOSTNAME@%@',
                                   hostname).replace('@%@PORT@%@', port)
        self.finished(request.id, response, mimetype='application/x-vnc')

    @simple_response
    def settings_get(self):
        """Return the current settings for a room"""

        if not self._italc.school or not self._italc.room:
            raise UMC_Error('no room selected')

        ucr.load()
        rule = ucr.get('proxy/filter/room/%s/rule' % self._italc.room, 'none')
        if rule == self._username:
            rule = 'custom'
        shareMode = ucr.get('samba/sharemode/room/%s' % self._italc.room,
                            'all')
        # load custom rule:
        key_prefix = 'proxy/filter/setting-user/%s/domain/whitelisted/' % self._username
        custom_rules = []
        for key in ucr:
            if key.startswith(key_prefix):
                custom_rules.append(ucr[key])

        printMode = ucr.get('samba/printmode/room/%s' % self._italc.room,
                            'default')
        # find AT jobs for the room and execute it to remove current settings
        jobs = atjobs.list(extended=True)
        for job in jobs:
            if job.comments.get(Instance.ATJOB_KEY, False) == self._italc.room:
                if job.execTime >= datetime.datetime.now():
                    self._ruleEndAt = job.execTime
                break
        else:
            self._ruleEndAt = None

        if rule == 'none' and shareMode == 'all' and printMode == 'default':
            self._ruleEndAt = None

        # find end of lesson
        period = self._lessons.current
        if period is None:
            if self._lessons.next:  # between two lessons
                period = self._lessons.next.end
            else:  # school is out ... 1 hour should be good (FIXME: configurable?)
                period = datetime.datetime.now() + datetime.timedelta(hours=1)
                period = period.time()
        else:
            period = period.end

        if self._ruleEndAt:
            time = self._ruleEndAt.time()
            for lesson in self._lessons.lessons:
                if time == lesson.begin:
                    period = lesson
                    break

        return {
            'internetRule': rule,
            'customRule': '\n'.join(custom_rules),
            'shareMode': shareMode,
            'printMode': printMode,
            'period': str(period),
        }

    @check_room_access
    @simple_response
    def finish_exam(self):
        """Finish the exam in the current room"""
        self._settings_set(printMode='default',
                           internetRule='none',
                           shareMode='all',
                           customRule='')
        _updateRoomInfo(self._italc.roomDN,
                        exam=None,
                        examDescription=None,
                        examEndTime=None)

    @sanitize(
        room=DNSanitizer(required=True),
        exam=StringSanitizer(required=True),
        examDescription=StringSanitizer(required=True),
        examEndTime=StringSanitizer(required=True),
    )
    @check_room_access
    @simple_response
    def start_exam(self, room, exam, examDescription, examEndTime):
        """Start an exam in the current room"""
        info = _readRoomInfo(room)
        if info.get('exam'):
            raise UMC_Error(
                _('In this room an exam is currently already written. Please select another room.'
                  ))

        _updateRoomInfo(self._italc.roomDN,
                        exam=exam,
                        examDescription=examDescription,
                        examEndTime=examEndTime)

    @sanitize(
        printMode=ChoicesSanitizer(['none', 'all', 'default'], required=True),
        internetRule=StringSanitizer(required=True),
        shareMode=ChoicesSanitizer(['home', 'all'], required=True),
        period=PeriodSanitizer(default='00:00', required=False),
        customRule=StringSanitizer(allow_none=True, required=False),
    )
    @check_room_access
    @simple_response
    def settings_set(self,
                     printMode,
                     internetRule,
                     shareMode,
                     period=None,
                     customRule=None):
        return self._settings_set(printMode, internetRule, shareMode, period,
                                  customRule)

    def _settings_set(self,
                      printMode,
                      internetRule,
                      shareMode,
                      period=None,
                      customRule=None):
        """Defines settings for a room"""

        if not self._italc.school or not self._italc.room:
            raise UMC_Error('no room selected')

        # find AT jobs for the room and execute it to remove current settings
        jobs = atjobs.list(extended=True)
        for job in jobs:
            if job.comments.get(Instance.ATJOB_KEY, False) == self._italc.room:
                job.rm()
                subprocess.call(shlex.split(job.command))

        roomInfo = _readRoomInfo(self._italc.roomDN)
        in_exam_mode = roomInfo.get('exam')

        # for the exam mode, remove current settings before setting new ones
        if in_exam_mode and roomInfo.get('cmd'):
            MODULE.info('unsetting room settings for exam (%s): %s' %
                        (roomInfo['exam'], roomInfo['cmd']))
            try:
                subprocess.call(shlex.split(roomInfo['cmd']))
            except (OSError, IOError):
                MODULE.warn(
                    'Failed to reinitialize current room settings: %s' %
                    roomInfo['cmd'])
            _updateRoomInfo(self._italc.roomDN, cmd=None)

        # reset to defaults. No atjob is necessary.
        if internetRule == 'none' and shareMode == 'all' and printMode == 'default':
            self._ruleEndAt = None
            self.reset_smb_connections()
            self.reload_cups()
            return

        # collect new settings
        vset = {}
        vappend = {}
        vunset = []
        vunset_now = []
        vextract = []
        hosts = self._italc.ipAddresses(students_only=True)

        # print mode
        if printMode in ('none', 'all'):
            vextract.append('samba/printmode/hosts/%s' % printMode)
            vappend[vextract[-1]] = hosts
            vextract.append('cups/printmode/hosts/%s' % printMode)
            vappend[vextract[-1]] = hosts
            vunset.append('samba/printmode/room/%s' % self._italc.room)
            vset[vunset[-1]] = printMode
        else:
            vunset_now.append('samba/printmode/room/%s' % self._italc.room)

        # share mode
        if shareMode == 'home':
            vunset.append('samba/sharemode/room/%s' % self._italc.room)
            vset[vunset[-1]] = shareMode
            vextract.append('samba/othershares/hosts/deny')
            vappend[vextract[-1]] = hosts
            vextract.append('samba/share/Marktplatz/hosts/deny')
            vappend[vextract[-1]] = hosts
        else:
            vunset_now.append('samba/sharemode/room/%s' % self._italc.room)

        # internet rule
        if internetRule != 'none':
            vextract.append('proxy/filter/room/%s/ip' % self._italc.room)
            vappend[vextract[-1]] = hosts
            if internetRule == 'custom':
                # remove old rules
                i = 1
                while True:
                    var = 'proxy/filter/setting-user/%s/domain/whitelisted/%d' % (
                        self._username, i)
                    if var in ucr:
                        vunset_now.append(var)
                        i += 1
                    else:
                        break
                vunset.append('proxy/filter/room/%s/rule' % self._italc.room)
                vset[vunset[-1]] = self._username
                vset['proxy/filter/setting-user/%s/filtertype' %
                     self._username] = 'whitelist-block'
                i = 1
                for domain in (customRule or '').split('\n'):
                    MODULE.info('Setting whitelist entry for domain %s' %
                                domain)
                    if not domain:
                        continue
                    parsed = urlparse.urlsplit(domain)
                    MODULE.info('Setting whitelist entry for domain %s' %
                                str(parsed))
                    if parsed.netloc:
                        vset[
                            'proxy/filter/setting-user/%s/domain/whitelisted/%d'
                            % (self._username, i)] = parsed.netloc
                        i += 1
                    elif parsed.path:
                        vset[
                            'proxy/filter/setting-user/%s/domain/whitelisted/%d'
                            % (self._username, i)] = parsed.path
                        i += 1
            else:
                vunset.append('proxy/filter/room/%s/rule' % self._italc.room)
                vset[vunset[-1]] = internetRule
        else:
            vunset_now.append('proxy/filter/room/%s/ip' % self._italc.room)
            vunset_now.append('proxy/filter/room/%s/rule' % self._italc.room)
        # write configuration
        # remove old values
        handler_unset(vunset_now)

        # append values
        ucr.load()
        MODULE.info('Merging UCR variables')
        for key, value in vappend.items():
            if ucr.get(key):
                old = set(ucr[key].split(' '))
                MODULE.info('Old value: %s' % old)
            else:
                old = set()
                MODULE.info('Old value empty')
            new = set(value)
            MODULE.info('New value: %s' % new)
            new = old.union(new)
            MODULE.info('Merged value of %s: %s' % (key, new))
            if not new:
                MODULE.info('Unset variable %s' % key)
                vunset.append(key)
            else:
                vset[key] = ' '.join(new)

        # Workaround for bug 30450:
        # if samba/printmode/hosts/none is not set but samba/printmode/hosts/all then all other hosts
        # are unable to print on samba shares. Solution: set empty value for .../none if no host is on deny list.
        varname = 'samba/printmode/hosts/none'
        if varname not in vset:
            ucr.load()
            if not ucr.get(varname):
                vset[varname] = '""'
        else:
            # remove empty items ('""') in list
            vset[varname] = ' '.join(
                [x for x in vset[varname].split(' ') if x != '""'])
        if varname in vunset:
            vunset.remove(varname)

        # set values
        ucr_vars = sorted('%s=%s' % x for x in vset.items())
        MODULE.info('Writing room rules: %s' % '\n'.join(ucr_vars))
        handler_set(ucr_vars)

        # create at job to remove settings
        unset_vars = ['-r %s' % quote(x) for x in vunset]
        MODULE.info('Will remove: %s' % ' '.join(unset_vars))
        extract_vars = ['-e %s' % quote(x) for x in vextract]
        MODULE.info('Will extract: %s' % ' '.join(extract_vars))

        cmd = '/usr/share/ucs-school-umc-computerroom/ucs-school-deactivate-rules %s %s %s' % (
            ' '.join(unset_vars), ' '.join(extract_vars), ' '.join(
                quote(x) for x in hosts))
        MODULE.info('command for reinitialization is: %s' % (cmd, ))

        if in_exam_mode:
            # Command for the exam mode to be executed manually when changing the settings again...
            _updateRoomInfo(self._italc.roomDN, cmd=cmd)
        else:
            starttime = datetime.datetime.now()
            MODULE.info('Now: %s' % starttime)
            MODULE.info('Endtime: %s' % period)
            starttime = starttime.replace(hour=period.hour,
                                          minute=period.minute,
                                          second=0,
                                          microsecond=0)
            while starttime < datetime.datetime.now(
            ):  # prevent problems due to intra-day limit
                starttime += datetime.timedelta(days=1)

            # AT job for the normal case
            MODULE.info('Remove settings at %s' % (starttime, ))
            atjobs.add(cmd, starttime, {Instance.ATJOB_KEY: self._italc.room})
            self._ruleEndAt = starttime

        self.reset_smb_connections()
        self.reload_cups()

    def reload_cups(self):
        if os.path.exists('/etc/init.d/cups'):
            MODULE.info('Reloading cups')
            if subprocess.call(['/etc/init.d/cups', 'reload']) != 0:
                MODULE.error(
                    'Failed to reload cups! Printer settings not applied.')

    def reset_smb_connections(self):
        smbstatus = SMB_Status()
        italc_users = [x.lower() for x in self._italc.users if x]
        MODULE.info('iTALC users: %s' % ', '.join(italc_users))
        for process in smbstatus:
            MODULE.info('SMB process: %s' % str(process))
            if process.username and process.username.lower() in italc_users:
                MODULE.info('Kill SMB process %s' % process.pid)
                os.kill(int(process.pid), signal.SIGTERM)

    @sanitize(
        server=StringSanitizer(required=True), )
    @check_room_access
    def demo_start(self, request):
        """Starts a presentation mode"""
        self._italc.startDemo(request.options['server'], True)
        self.finished(request.id, True)

    @check_room_access
    def demo_stop(self, request):
        """Stops a presentation mode"""

        self._italc.stopDemo()
        self.finished(request.id, True)

    @sanitize(
        state=ChoicesSanitizer(['poweroff', 'poweron', 'restart']),
        computer=ComputerSanitizer(required=True),
    )
    @check_room_access
    @prevent_ucc(
        condition=lambda request: request.options['state'] != 'poweron')
    @simple_response
    def computer_state(self, computer, state):
        """Stops, starts or restarts a computer"""

        if state == 'poweroff':
            computer.powerOff()
        elif state == 'poweron':
            computer.powerOn()
        elif state == 'restart':
            computer.restart()
        return True

    @check_room_access
    @sanitize(
        computer=ComputerSanitizer(required=True), )
    @prevent_ucc
    @simple_response
    def user_logout(self, computer):
        """Log out the user at the given computer"""

        computer.logOut()
        return True

    @simple_response
    def plugins_load(self):
        plugins = {'buttons': []}
        for plugin in self._plugins.values():
            plugins['buttons'].append(plugin.button())
        return plugins

    @check_room_access
    @sanitize(
        plugin=StringSanitizer(required=True),
        computer=StringSanitizer(required=True),
    )
    def plugins_execute(self, request):
        plugin = self._plugins.get(request.options['plugin'])
        if not plugin:
            raise UMC_Error('Plugin not found.')
        result = plugin.execute(request.options['computer'])
        self.finished(request.id, result)
Beispiel #3
0
class Instance(SchoolBaseModule, SchoolImport):

	def init(self):
		super(Instance, self).init()
		add_module_logger_to_schoollib()

	@simple_response
	def is_singlemaster(self):
		return ucr.is_true('ucsschool/singlemaster', False)

	@sanitize(
		schooldc=StringSanitizer(required=True, regex_pattern=re.compile(r'^[a-zA-Z](([a-zA-Z0-9-_]*)([a-zA-Z0-9]$))?$')),
		schoolou=StringSanitizer(required=True, regex_pattern=re.compile(r'^[a-zA-Z0-9](([a-zA-Z0-9_]*)([a-zA-Z0-9]$))?$')),
	)
	@simple_response
	def move_dc(self, schooldc, schoolou):
		params = ['--dcname', schooldc, '--ou', schoolou]
		return_code, stdout = self._run_script(SchoolImport.MOVE_DC_SCRIPT, params, True)
		return {'success': return_code == 0, 'message': stdout}

	@simple_response
	def computer_types(self):
		ret = []
		computer_types = [WindowsComputer, MacComputer, IPComputer]
		try:
			import univention.admin.handlers.computers.ucc as ucc
			del ucc
		except ImportError:
			pass
		else:
			computer_types.insert(1, UCCComputer)
		for computer_type in computer_types:
			ret.append({'id': computer_type._meta.udm_module_short, 'label': computer_type.type_name})
		return ret

	@response
	@LDAP_Connection()
	def share_servers(self, request, ldap_user_read=None):
		# udm/syntax/choices UCSSchool_Server_DN
		ret = [{'id': '', 'label': ''}]
		for module in ['computers/domaincontroller_master', 'computers/domaincontroller_backup', 'computers/domaincontroller_slave', 'computers/memberserver']:
			for obj in udm_modules.lookup(module, None, ldap_user_read, scope='sub'):
				obj.open()
				ret.append({'id': obj.dn, 'label': obj.info.get('fqdn', obj.info['name'])})
		return ret

	@sanitize_object(**{
		'$dn$': DNSanitizer(required=True),
	})
	@response
	@LDAP_Connection()
	def _get_obj(self, request, ldap_user_read=None):
		ret = []
		for obj in iter_objects_in_request(request, ldap_user_read, True):
			MODULE.process('Getting %r' % (obj))
			obj = obj.from_dn(obj.old_dn, obj.school, ldap_user_read)
			ret.append(obj.to_dict())
		return ret

	@response
	@LDAP_Connection(USER_READ, USER_WRITE, ADMIN_WRITE)
	def _create_obj(self, request, ldap_user_read=None, ldap_user_write=None, ldap_admin_write=None):
		# Bug #44641: workaround with security implications!
		if ucr.is_true('ucsschool/wizards/schoolwizards/workaround/admin-connection'):
			ldap_user_write = ldap_admin_write

		ret = []
		for obj in iter_objects_in_request(request, ldap_user_write):
			MODULE.process('Creating %r' % (obj,))
			obj.validate(ldap_user_read)
			if obj.errors:
				ret.append({'result': {'message': obj.get_error_msg()}})
				MODULE.process('Validation failed %r' % (ret[-1],))
				continue
			try:
				if obj.create(ldap_user_write, validate=False):
					ret.append(True)
				else:
					ret.append({'result': {'message': _('"%s" already exists!') % obj.name}})
			except uldapBaseException as exc:
				ret.append({'result': {'message': get_exception_msg(exc)}})
				MODULE.process('Creation failed %r' % (ret[-1],))
		return ret

	@sanitize_object(**{
		'$dn$': DNSanitizer(required=True),
	})
	@response
	@LDAP_Connection(USER_READ, USER_WRITE, ADMIN_WRITE)
	def _modify_obj(self, request, ldap_user_read=None, ldap_user_write=None, ldap_admin_write=None):
		# Bug #44641: workaround with security implications!
		if ucr.is_true('ucsschool/wizards/schoolwizards/workaround/admin-connection'):
			ldap_user_write = ldap_admin_write

		ret = []
		for obj in iter_objects_in_request(request, ldap_user_write, True):
			MODULE.process('Modifying %r' % (obj))
			obj.validate(ldap_user_read)
			if obj.errors:
				ret.append({'result': {'message': obj.get_error_msg()}})
				continue
			try:
				obj.modify(ldap_user_write, validate=False)
			except uldapBaseException as exc:
				ret.append({'result': {'message': get_exception_msg(exc)}})
			else:
				ret.append(True)  # no changes? who cares?
		return ret

	@sanitize_object(**{
		'$dn$': DNSanitizer(required=True),
	})
	@response
	@LDAP_Connection(USER_READ, USER_WRITE, ADMIN_WRITE)
	def _delete_obj(self, request, ldap_user_read=None, ldap_user_write=None, ldap_admin_write=None):
		# Bug #44641: workaround with security implications!
		if ucr.is_true('ucsschool/wizards/schoolwizards/workaround/admin-connection'):
			ldap_user_write = ldap_admin_write

		ret = []
		for obj in iter_objects_in_request(request, ldap_user_write, True):
			obj.name = obj.get_name_from_dn(obj.old_dn)
			MODULE.process('Deleting %r' % (obj))
			if obj.remove(ldap_user_write):
				ret.append(True)
			else:
				ret.append({'result': {'message': _('"%s" does not exist!') % obj.name}})
		return ret

	def _get_all(self, klass, school, filter_str, lo):
		if school:
			schools = [School.cache(school)]
		else:
			schools = School.from_binddn(lo)
		objs = []
		for school in schools:
			try:
				objs.extend(klass.get_all(lo, school.name, filter_str=filter_str, easy_filter=True))
			except noObject as exc:
				MODULE.error('Could not get all objects of %r: %r' % (klass.__name__, exc))
		return [obj.to_dict() for obj in objs]

	@sanitize(
		school=StringSanitizer(required=True),
		type=ChoicesSanitizer(['all'] + USER_TYPES.keys(), required=True),
		filter=StringSanitizer(default=''),
	)
	@response
	@LDAP_Connection()
	def get_users(self, request, ldap_user_read=None):
		school = request.options['school']
		user_class = USER_TYPES.get(request.options['type'], User)
		return self._get_all(user_class, school, request.options.get('filter'), ldap_user_read)

	get_user = _get_obj
	modify_user = _modify_obj
	create_user = _create_obj

	@sanitize_object(**{
		'remove_from_school': SchoolSanitizer(required=True),
		'$dn$': DNSanitizer(required=True),
	})
	@response
	@LDAP_Connection(USER_READ, USER_WRITE, ADMIN_WRITE)
	def delete_user(self, request, ldap_user_read=None, ldap_user_write=None, ldap_admin_write=None):
		# Bug #44641: workaround with security implications!
		if ucr.is_true('ucsschool/wizards/schoolwizards/workaround/admin-connection'):
			ldap_user_write = ldap_admin_write

		ret = []
		for obj_props in request.options:
			obj_props = obj_props['object']
			try:
				obj = User.from_dn(obj_props['$dn$'], None, ldap_user_write)
			except noObject:
				raise UMC_Error(_('The %s %r does not exists or might have been removed in the meanwhile.') % (getattr(User, 'type_name', None) or User.__name__, User.get_name_from_dn(obj_props['$dn$'])))
			school = obj_props['remove_from_school']
			success = obj.remove_from_school(school, ldap_user_write)
			# obj.old_dn is None when the ucsschool lib has deleted the user after the last school was removed from it
			if success and obj.old_dn is not None:
				success = obj.modify(ldap_user_write)
			if not success:
				success = {'result': {'message': _('Failed to remove user from school.')}}
			ret.append(success)
		return ret

	@sanitize(
		school=StringSanitizer(required=True),
		type=ChoicesSanitizer(['all'] + COMPUTER_TYPES.keys(), required=True),
		filter=StringSanitizer(default=''),
	)
	@response
	@LDAP_Connection()
	def get_computers(self, request, ldap_user_read=None):
		school = request.options['school']
		computer_class = COMPUTER_TYPES.get(request.options['type'], SchoolComputer)
		return self._get_all(computer_class, school, request.options.get('filter'), ldap_user_read)

	get_computer = _get_obj
	modify_computer = _modify_obj
	create_computer = _create_obj
	delete_computer = _delete_obj

	@sanitize(
		school=StringSanitizer(required=True),
		filter=StringSanitizer(default=''),
	)
	@response
	@LDAP_Connection()
	def get_classes(self, request, ldap_user_read=None):
		school = request.options['school']
		return self._get_all(SchoolClass, school, request.options.get('filter'), ldap_user_read)

	get_class = _get_obj
	modify_class = _modify_obj
	create_class = _create_obj
	delete_class = _delete_obj

	@response
	@LDAP_Connection()
	def get_schools(self, request, ldap_user_read=None):
		schools = School.get_all(ldap_user_read, filter_str=request.options.get('filter'), easy_filter=True)
		return [school.to_dict() for school in schools]

	get_school = _get_obj
	modify_school = _modify_obj
	create_school = _create_obj
	delete_school = _delete_obj
Beispiel #4
0
class Instance(SchoolBaseModule):
    @sanitize(
        school=SchoolSanitizer(required=True),
        pattern=StringSanitizer(default=''),
    )
    @LDAP_Connection()
    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)

    @sanitize(pattern=StringSanitizer(default=''),
              school=SchoolSanitizer(required=True))
    @LDAP_Connection()
    def query(self, request, ldap_user_read=None, ldap_position=None):
        klasses = [get_group_class(request)]
        if klasses[0] is Teacher:
            klasses.append(TeachersAndStaff)
        groups = []
        for klass in klasses:
            groups.extend(
                klass.get_all(ldap_user_read,
                              request.options['school'],
                              filter_str=request.options['pattern'],
                              easy_filter=True))
        self.finished(request.id, [group.to_dict() for group in groups])

    @sanitize(StringSanitizer(required=True))
    @LDAP_Connection()
    def get(self, request, ldap_user_read=None, ldap_position=None):
        klass = get_group_class(request)
        for group_dn in request.options:
            break
        try:
            group = klass.from_dn(group_dn, None, ldap_user_read)
        except udm_exceptions.noObject:
            raise UMC_Error('unknown object')

        school = group.school
        result = group.to_dict()

        if request.flavor == 'teacher':
            classes = SchoolClass.get_all(ldap_user_read,
                                          school,
                                          filter_str=filter_format(
                                              'uniqueMember=%s', (group_dn, )))
            result['classes'] = [{
                'id': class_.dn,
                'label': class_.get_relative_name()
            } for class_ in classes]
            self.finished(request.id, [result])
            return
        result['members'] = self._filter_members(request, group,
                                                 result.pop('users', []),
                                                 ldap_user_read)

        self.finished(request.id, [
            result,
        ])

    @staticmethod
    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

    @sanitize(DictSanitizer(dict(object=DictSanitizer({}, required=True))))
    @LDAP_Connection(USER_READ, MACHINE_WRITE)
    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)

    @sanitize(DictSanitizer(dict(object=DictSanitizer({}, required=True))))
    @only_workgroup_admin
    @LDAP_Connection(USER_READ, USER_WRITE)
    def add(self,
            request,
            ldap_user_write=None,
            ldap_user_read=None,
            ldap_position=None):
        for group in request.options:
            group = group['object']
            break
        try:
            grp = {}
            grp['school'] = group['school']
            grp['name'] = '%(school)s-%(name)s' % group
            grp['description'] = group['description']
            grp['users'] = group['members']

            grp = WorkGroup(**grp)

            success = grp.create(ldap_user_write)
            if not success and grp.exists(ldap_user_read):
                raise UMC_Error(
                    _('The workgroup %r already exists!') % grp.name)
        except udm_exceptions.base as exc:
            MODULE.process(
                'An error occurred while creating the group "%s": %s' %
                (group['name'], exc.message))
            raise UMC_Error(_('Failed to create group (%s).') % exc.message)

        self.finished(request.id, success)

    @sanitize(DictSanitizer(dict(object=ListSanitizer(min_elements=1))))
    @only_workgroup_admin
    @LDAP_Connection(USER_READ, USER_WRITE)
    def remove(self,
               request,
               ldap_user_write=None,
               ldap_user_read=None,
               ldap_position=None):
        """Deletes a workgroup"""
        for group_dn in request.options:
            group_dn = group_dn['object'][0]
            break

        group = WorkGroup.from_dn(group_dn, None, ldap_user_write)
        if not group.school:
            raise UMC_Error('Group must within the scope of a school OU: %s' %
                            group_dn)

        try:
            success = group.remove(ldap_user_write)
        except udm_exceptions.base as exc:
            MODULE.error('Could not remove group "%s": %s' % (group.dn, exc))
            self.finished(request.id, [{
                'success': False,
                'message': str(exc)
            }])
            return

        self.finished(request.id, [{'success': success}])

    @sanitize(
        **{
            '$dn$': StringSanitizer(required=True),
            'classes': ListSanitizer(StringSanitizer(required=True),
                                     required=True),
            'school': SchoolSanitizer(required=True),
        })
    @LDAP_Connection(USER_READ, MACHINE_WRITE)
    def add_teacher_to_classes(self,
                               request,
                               ldap_machine_write=None,
                               ldap_user_read=None,
                               ldap_position=None):
        teacher = request.options['$dn$']
        classes = set(request.options['classes'])
        try:
            teacher = Teacher.from_dn(teacher, None, ldap_machine_write)
            if not teacher.is_teacher(ldap_machine_write):
                raise udm_exceptions.noObject()
        except udm_exceptions.noObject:
            raise UMC_Error('The user is not a teacher.')

        original_classes = set([
            x.dn for x in SchoolClass.get_all(
                ldap_machine_write, request.options['school'],
                filter_format('uniqueMember=%s', (teacher.dn, )))
        ])
        classes_to_remove = original_classes - classes
        classes_to_add = classes - original_classes

        failed = []
        for classdn in (classes_to_add | classes_to_remove):
            try:
                class_ = SchoolClass.from_dn(classdn, teacher.school,
                                             ldap_machine_write)
            except udm_exceptions.noObject:
                failed.append(classdn)
                continue

            if classdn in classes_to_add and teacher.dn not in class_.users:
                class_.users.append(teacher.dn)
            elif classdn in classes_to_remove and teacher.dn in class_.users:
                class_.users.remove(teacher.dn)
            try:
                if not class_.modify(ldap_machine_write):
                    failed.append(classdn)
            except udm_exceptions.base as exc:
                MODULE.error('Could not add teacher %s to class %s: %s' %
                             (teacher.dn, classdn, exc))
                failed.append(classdn)
        self.finished(request.id, not any(failed))
Beispiel #5
0
class Instance(SchoolBaseModule):
    def __init__(self):
        SchoolBaseModule.__init__(self)
        self._tmpDir = None
        self._progress_state = util.Progress()
        self._lessons = SchoolLessons()

    def init(self):
        SchoolBaseModule.init(self)
        # initiate paths for data distribution
        util.distribution.initPaths()

    def destroy(self):
        # clean temporary data
        self._cleanTmpDir()

    def _cleanTmpDir(self):
        # copied from distribution module
        # clean up the temporary upload directory
        if self._tmpDir:
            MODULE.info('Clean up temporary directory: %s' % self._tmpDir)
            shutil.rmtree(self._tmpDir, ignore_errors=True)
            self._tmpDir = None

    @file_upload
    @sanitize(
        DictSanitizer(dict(
            filename=StringSanitizer(required=True),
            tmpfile=StringSanitizer(required=True),
        ),
                      required=True))
    def upload(self, request):
        # copied from distribution module
        # create a temporary upload directory, if it does not already exist
        if not self._tmpDir:
            self._tmpDir = tempfile.mkdtemp(prefix='ucsschool-exam-upload-')
            MODULE.info('Created temporary directory: %s' % self._tmpDir)

        for file in request.options:
            filename = self.__workaround_filename_bug(file)
            destPath = os.path.join(self._tmpDir, filename)
            MODULE.info('Received file %r, saving it to %r' %
                        (file['tmpfile'], destPath))
            shutil.move(file['tmpfile'], destPath)

        self.finished(request.id, None)

    def __workaround_filename_bug(self, file):
        # the following code block is a heuristic to support both: fixed and unfixed Bug #37716
        filename = file['filename']
        try:
            # The UMC-Webserver decodes filename in latin-1, need to revert
            filename = filename.encode('ISO8859-1')
        except UnicodeEncodeError:
            # we got non-latin characters, Bug #37716 is fixed and string contains e.g. '→'
            filename = file['filename'].encode('UTF-8')
        else:
            # the string contains at least no non-latin1 characters
            try:
                # try if the bytes could be UTF-8
                # can't fail if Bug #37716 is fixed
                filename.decode('UTF-8')
            except UnicodeDecodeError:
                filename = file['filename'].encode(
                    'UTF-8')  # Bug #37716 was fixed
        MODULE.info('Detected filename %r as %r' %
                    (file['filename'], filename))
        # the code block can be removed and replaced by filename = file['filename'].encode('UTF-8') after Bug #37716
        return filename

    @simple_response
    def internetrules(self):
        # copied from computerroom module
        """Returns a list of available internet rules"""
        return [x.name for x in internetrules.list()]

    @simple_response
    def lesson_end(self):
        current = self._lessons.current
        if current is not None:
            return current.end.strftime('%H:%M')
        return (datetime.datetime.now() +
                datetime.timedelta(minutes=45)).strftime('%H:%M')

    @simple_response
    def progress(self):
        return self._progress_state.poll()

    @sanitize(
        name=StringSanitizer(required=True),
        room=StringSanitizer(required=True),
        school=SchoolSanitizer(required=True),
        directory=StringSanitizer(required=True),
        shareMode=StringSanitizer(required=True),
        internetRule=StringSanitizer(required=True),
        customRule=StringSanitizer(),
        examEndTime=StringSanitizer(required=True),
        recipients=ListSanitizer(StringSanitizer(minimum=1), required=True),
        files=ListSanitizer(StringSanitizer()),
    )
    @require_password
    @LDAP_Connection()
    def start_exam(self, request, ldap_user_read=None, ldap_position=None):
        # reset the current progress state
        # steps:
        #   5  -> for preparing exam room
        #   25 -> for cloning users
        #   25 -> for each replicated users + copy of the profile directory
        #   20 -> distribution of exam files
        #   10  -> setting room properties
        progress = self._progress_state
        progress.reset(85)
        progress.component(_('Initializing'))

        # create that holds a reference to project, otherwise _thread() cannot
        # set the project variable in the scope of start_exam:
        my = type("", (), dict(project=None))()

        # create a User object for the teacher
        # perform this LDAP operation outside the thread, to avoid tracebacks
        # in case of an LDAP timeout
        sender = util.distribution.openRecipients(self.user_dn, ldap_user_read)
        if not sender:
            raise UMC_Error(
                _('Could not authenticate user "%s"!') % self.user_dn)

        def _thread():
            # make sure that a project with the same name does not exist
            directory = request.options['directory']
            # get absolute path of project file and test for existance
            fn_test_project = util.distribution.Project.sanitize_project_filename(
                directory)
            if os.path.exists(fn_test_project):
                raise UMC_Error(
                    _('An exam with the name "%s" already exists. Please choose a different name for the exam.'
                      ) % (directory, ))

            # validate the project data and save project
            my.project = util.distribution.Project(
                dict(
                    name=directory,
                    description=request.options['name'],
                    files=request.options.get('files'),
                    sender=sender,
                ))
            my.project.validate()
            my.project.save()

            # copy files into project directory
            if self._tmpDir:
                for ifile in my.project.files:
                    isrc = os.path.join(self._tmpDir, ifile)
                    itarget = os.path.join(my.project.cachedir, ifile)
                    if os.path.exists(isrc):
                        # copy file to cachedir
                        shutil.move(isrc, itarget)
                        os.chown(itarget, 0, 0)

            # open a new connection to the master UMC
            try:
                master = ucr['ldap/master']
                client = Client(master)
                client.authenticate_with_machine_account()
            except (ConnectionError, HTTPError) as exc:
                MODULE.error('Could not connect to UMC on %s: %s' %
                             (master, exc))
                raise UMC_Error(
                    _('Could not connect to master server %s.') %
                    ucr.get('ldap/master'))

            # mark the computer room for exam mode
            progress.component(
                _('Preparing the computer room for exam mode...'))
            client.umc_command(
                'schoolexam-master/set-computerroom-exammode',
                dict(
                    school=request.options['school'],
                    roomdn=request.options['room'],
                )).result  # FIXME: no error handling
            progress.add_steps(5)

            # read all recipients and fetch all user objects
            users = []
            for idn in request.options['recipients']:
                ientry = util.distribution.openRecipients(idn, ldap_user_read)
                if not ientry:
                    continue
                # recipients can in theory be users or groups
                members = []
                if isinstance(ientry, util.distribution.User):
                    members = [ientry]
                elif isinstance(ientry, util.distribution.Group):
                    members = ientry.members
                for entry in members:
                    # ignore exam users
                    user = User.from_dn(entry.dn, None, ldap_user_read)
                    if not user.is_exam_student(ldap_user_read):
                        users.append(entry)

            # start to create exam user accounts
            progress.component(_('Preparing exam accounts'))
            percentPerUser = 25.0 / (1 + len(users))
            examUsers = set()
            student_dns = set()
            usersReplicated = set()
            for iuser in users:
                progress.info(
                    '%s, %s (%s)' %
                    (iuser.lastname, iuser.firstname, iuser.username))
                try:
                    ires = client.umc_command(
                        'schoolexam-master/create-exam-user',
                        dict(
                            school=request.options['school'],
                            userdn=iuser.dn,
                        )).result
                    examuser_dn = ires.get('examuserdn')
                    examUsers.add(examuser_dn)
                    student_dns.add(iuser.dn)
                    MODULE.info('Exam user has been created: %r' % examuser_dn)
                except (ConnectionError, HTTPError) as exc:
                    MODULE.warn(
                        'Could not create exam user account for %r: %s' %
                        (iuser.dn, exc))

                # indicate the the user has been processed
                progress.add_steps(percentPerUser)

            client.umc_command(
                'schoolexam-master/add-exam-users-to-groups',
                dict(
                    users=list(student_dns),
                    school=request.options['school'],
                ))

            progress.add_steps(percentPerUser)

            # wait for the replication of all users to be finished
            progress.component(_('Preparing user home directories'))
            recipients = []  # list of User objects for all exam users
            openAttempts = 30 * 60  # wait max. 30 minutes for replication
            while (len(examUsers) > len(usersReplicated)) and (openAttempts >
                                                               0):
                openAttempts -= 1
                MODULE.info(
                    'waiting for replication to be finished, %s user objects missing'
                    % (len(examUsers) - len(usersReplicated)))
                for idn in examUsers - usersReplicated:
                    try:
                        ldap_user_read.get(idn, required=True)
                    except ldap.NO_SUCH_OBJECT:
                        continue  # not replicated yet
                    iuser = util.distribution.openRecipients(
                        idn, ldap_user_read)
                    if not iuser:
                        continue  # not a users/user object
                    MODULE.info('user has been replicated: %s' % idn)

                    # call hook scripts
                    if 0 != subprocess.call([
                            '/bin/run-parts', CREATE_USER_POST_HOOK_DIR,
                            '--arg', iuser.username, '--arg', iuser.dn,
                            '--arg', iuser.homedir
                    ]):
                        raise ValueError(
                            'failed to run hook scripts for user %r' %
                            (iuser.username))

                    # store User object in list of final recipients
                    recipients.append(iuser)

                    # mark the user as replicated
                    usersReplicated.add(idn)
                    progress.info(
                        '%s, %s (%s)' %
                        (iuser.lastname, iuser.firstname, iuser.username))
                    progress.add_steps(percentPerUser)

                # wait a second
                time.sleep(1)

            progress.add_steps(percentPerUser)

            if openAttempts <= 0:
                MODULE.error(
                    'replication timeout - %s user objects missing: %r ' %
                    ((len(examUsers) - len(usersReplicated)),
                     (examUsers - usersReplicated)))
                raise UMC_Error(
                    _('Replication timeout: could not create all exam users'))

            # update the final list of recipients
            my.project.recipients = recipients
            my.project.save()

            # update local NSS group cache
            if ucr.is_true('nss/group/cachefile', True):
                cmd = ['/usr/lib/univention-pam/ldap-group-to-file.py']
                if ucr.is_true('nss/group/cachefile/check_member', False):
                    cmd.append('--check_member')
                MODULE.info('Updating local nss group cache...')
                if subprocess.call(cmd):
                    MODULE.error('Updating local nss group cache failed: %s' %
                                 ' '.join(cmd))
                else:
                    MODULE.info(
                        'Update of local nss group cache finished successfully.'
                    )

            # distribute exam files
            progress.component(_('Distributing exam files'))
            progress.info('')
            my.project.distribute()
            progress.add_steps(20)

            # prepare room settings via UMCP...
            #   first step: acquire room
            #   second step: adjust room settings
            progress.component(_('Prepare room settings'))
            try:
                user_client = Client(None, self.username, self.password)
            except (ConnectionError, HTTPError) as exc:
                MODULE.warn('Authentication failed: %s' % (exc, ))
                raise UMC_Error(_('Could not connect to local UMC server.'))

            room = request.options['room']
            MODULE.info('Acquire room: %s' % (room, ))
            user_client.umc_command('computerroom/room/acquire',
                                    dict(
                                        room=request.options['room'], )).result
            progress.add_steps(1)
            MODULE.info('Adjust room settings:\n%s' % '\n'.join(
                ['  %s=%s' % (k, v) for k, v in request.options.iteritems()]))
            user_client.umc_command(
                'computerroom/exam/start',
                dict(
                    room=room,
                    examDescription=request.options['name'],
                    exam=directory,
                    examEndTime=request.options.get('examEndTime'),
                )).result
            progress.add_steps(4)
            user_client.umc_command(
                'computerroom/settings/set',
                dict(
                    room=room,
                    internetRule=request.options['internetRule'],
                    customRule=request.options.get('customRule'),
                    shareMode=request.options['shareMode'],
                    printMode='default',
                )).result
            progress.add_steps(5)

        def _finished(thread, result, request):
            # mark the progress state as finished
            progress.info(_('finished...'))
            progress.finish()

            # finish the request at the end in order to force the module to keep
            # running until all actions have been completed
            success = not isinstance(result, BaseException)
            response = dict(success=success)
            if success:
                # remove uploaded files from cache
                self._cleanTmpDir()
            else:
                msg = str(result)
                if not isinstance(result, UMC_Error):
                    response = result
                    msg = ''.join(traceback.format_exception(*thread.exc_info))
                progress.error(msg)

                # in case a distribution project has already be written to disk, purge it
                if my.project:
                    my.project.purge()

            self.thread_finished_callback(thread, response, request)

        thread = notifier.threads.Simple('start_exam', _thread,
                                         notifier.Callback(_finished, request))
        thread.run()

    @sanitize(
        exam=StringSanitizer(required=True), )
    @simple_response
    def collect_exam(self, exam):
        project = util.distribution.Project.load(exam)
        if not project:
            raise UMC_Error(_('No files have been distributed'))

        project.collect()
        return True

    @sanitize(
        room=DNSanitizer(required=True), )
    @LDAP_Connection()
    def validate_room(self, request, ldap_user_read=None, ldap_position=None):
        error = None
        dn = request.options['room']
        room = ComputerRoom.from_dn(dn, None, ldap_user_read)
        if not room.hosts:
            # FIXME: raise UMC_Error()
            error = _(
                'Room "%s" does not contain any computers. Empty rooms may not be used to start an exam.'
            ) % room.get_relative_name()
        self.finished(request.id, error)

    @sanitize(
        room=StringSanitizer(required=True),
        exam=StringSanitizer(required=True),
    )
    def finish_exam(self, request):
        # reset the current progress state
        # steps:
        #   10 -> collecting exam files
        #   5 -> for preparing exam room
        #   25 -> for cloning users
        progress = self._progress_state
        progress.reset(40)
        progress.component(_('Initializing'))

        # try to open project file
        project = util.distribution.Project.load(request.options.get('exam'))
        if not project:
            # the project file does not exist... ignore problem
            MODULE.warn(
                'The project file for exam %s does not exist. Ignoring and finishing exam mode.'
                % request.options.get('exam'))

        def _thread():
            # perform all actions inside a thread...
            # collect files
            progress.component(_('Collecting exam files...'))
            if project:
                project.collect()
            progress.add_steps(10)

            # open a new connection to the master UMC
            master = ucr['ldap/master']
            try:
                client = Client(master)
                client.authenticate_with_machine_account()
            except (ConnectionError, HTTPError) as exc:
                MODULE.error('Could not connect to UMC on %s: %s' %
                             (master, exc))
                raise UMC_Error(
                    _('Could not connect to master server %s.') % (master, ))

            school = SchoolSearchBase.getOU(request.options['room'])

            # unset exam mode for the given computer room
            progress.component(_('Configuring the computer room...'))
            client.umc_command(
                'schoolexam-master/unset-computerroom-exammode',
                dict(
                    roomdn=request.options['room'],
                    school=school,
                )).result
            progress.add_steps(5)

            # delete exam users accounts
            if project:
                # get a list of user accounts in parallel exams
                parallelUsers = dict([
                    (iuser.username, iproject.description)
                    for iproject in util.distribution.Project.list()
                    if iproject.name != project.name
                    for iuser in iproject.recipients
                ])

                progress.component(_('Removing exam accounts'))
                percentPerUser = 25.0 / (1 + len(project.recipients))
                for iuser in project.recipients:
                    progress.info(
                        '%s, %s (%s)' %
                        (iuser.lastname, iuser.firstname, iuser.username))
                    try:
                        if iuser.username not in parallelUsers:
                            # remove first the home directory
                            shutil.rmtree(iuser.unixhome, ignore_errors=True)

                            # remove LDAP user entry
                            client.umc_command(
                                'schoolexam-master/remove-exam-user',
                                dict(
                                    userdn=iuser.dn,
                                    school=school,
                                )).result
                            MODULE.info('Exam user has been removed: %s' %
                                        iuser.dn)
                        else:
                            MODULE.process(
                                'Cannot remove the user account %s as it is registered for the running exam "%s", as well'
                                % (iuser.dn, parallelUsers[iuser.username]))
                    except (ConnectionError, HTTPError) as e:
                        MODULE.warn(
                            'Could not remove exam user account %s: %s' %
                            (iuser.dn, e))

                    # indicate the user has been processed
                    progress.add_steps(percentPerUser)

                progress.add_steps(percentPerUser)

        def _finished(thread, result):
            # mark the progress state as finished
            progress.info(_('finished...'))
            progress.finish()

            # running until all actions have been completed
            if isinstance(result, BaseException):
                msg = ''.join(traceback.format_exception(*thread.exc_info))
                MODULE.error('Exception during exam_finish: %s' % msg)
                self.finished(request.id, dict(success=False))
                progress.error(
                    _('An unexpected error occurred during the preparation: %s'
                      ) % result)
            else:
                self.finished(request.id, dict(success=True))

                if project:
                    # purge project
                    project.purge()

                # remove uploaded files from cache
                self._cleanTmpDir()

        thread = notifier.threads.Simple('start_exam', _thread, _finished)
        thread.run()
Beispiel #6
0
class Instance(SchoolBaseModule):
    def init(self):
        global CUPSPDF_DIR, CUPSPDF_USERSUBDIR
        SchoolBaseModule.init(self)
        CUPSPDF_DIR, CUPSPDF_USERSUBDIR = os.path.normpath(
            ucr.get('cups/cups-pdf/directory',
                    '/var/spool/cups-pdf/%U')).split('%U')
        # create directory if it does not exist
        try:
            if not os.path.exists(CUPSPDF_DIR):
                os.makedirs(DISTRIBUTION_DATA_PATH, 0o755)
        except (OSError, IOError) as exc:
            MODULE.error('error occured while creating %s: %s' %
                         (CUPSPDF_DIR, exc))
        self.fqdn = '%s.%s' % (ucr.get('hostname'), ucr.get('domainname'))
        self.pw_callback_bad_password = False

    def _get_path(self, username, printjob):
        printjob = printjob.replace('/', '')
        username = username.replace('/', '')
        path = os.path.join(CUPSPDF_DIR, username, CUPSPDF_USERSUBDIR,
                            printjob)
        if not os.path.realpath(path).startswith(
                os.path.realpath(CUPSPDF_DIR)):
            raise UMC_Error(_('Invalid file'))
        return path

    def _get_all_username_variants(self, username):
        """
		Checks for print job directories for the given username regardless of
		the case of the directory name.
		"""
        username = username.replace('/', '')
        all_user_dirs = os.walk(CUPSPDF_DIR).next()[1]
        return [x for x in all_user_dirs if x.lower() == username.lower()]

    @sanitize(
        school=SchoolSanitizer(required=True), )
    @LDAP_Connection()
    def printers(self, request, ldap_user_read=None):
        """List all available printers except PDF printers
		return: [{'id': <spool host>://<printer name>, 'label': <display name>}, ...]
		"""
        try:
            printers = udm_modules.lookup(
                'shares/printer',
                None,
                ldap_user_read,
                scope='sub',
                base=School.get_search_base(
                    request.options['school']).printers)
        except udm_errors.noObject:
            printers = []

        result = []
        for prt in printers:
            # ignore PDF printers
            uri = prt.info.get('uri', [])
            if uri and uri[0].startswith('cups-pdf'):
                continue
            name = prt.info['name']
            spool_host = prt.info['spoolHost'][0]
            # allways prefer myself
            if self.fqdn in prt.info['spoolHost']:
                spool_host = self.fqdn
            result.append({
                'id': '%s://%s' % (spool_host, name),
                'label': name
            })
        self.finished(request.id, result)

    @sanitize(
        **{
            'school': SchoolSanitizer(required=True),
            'class': StringSanitizer(required=True),
            'pattern': StringSanitizer(required=True),
        })
    @LDAP_Connection()
    def query(self, request, ldap_user_read=None, ldap_position=None):
        """Searches for print jobs"""

        klass = request.options.get('class')
        if klass in (None, 'None'):
            klass = None
        students = self._users(ldap_user_read,
                               request.options['school'],
                               group=klass,
                               user_type='student',
                               pattern=request.options.get('pattern', ''))

        try:
            students.append(
                User.from_dn(self.user_dn, None,
                             ldap_user_read).get_udm_object(ldap_user_read))
        except udm_errors.noObject:
            MODULE.warn(
                'Could not get user object of teacher %r. Is it a UCS@school user?'
                % (self.user_dn, ))

        printjoblist = []

        for student in students:
            username = student.info['username']
            path_username = dict(
                (self._get_path(username, ''), username)
                for username in self._get_all_username_variants(username))
            for user_path, username in path_username.iteritems():
                printjoblist.extend(
                    Printjob(student, username, document).json()
                    for document in glob.glob(os.path.join(user_path, '*.pdf'))
                    if os.path.isfile(document))

        self.finished(request.id, printjoblist)

    @allow_get_request
    @sanitize(
        username=StringSanitizer(required=True),
        printjob=StringSanitizer(required=True),
    )
    def download(self, request):
        """Searches for print jobs

		requests.options = {}
		'username' -- owner of the print job
		'printjob' -- relative filename of the print job

		return: <PDF document>
		"""
        path = self._get_path(request.options['username'],
                              request.options['printjob'])

        if not os.path.exists(path):
            raise UMC_Error(_('Invalid file'))

        with open(path) as fd:
            self.finished(request.id, fd.read(), mimetype='application/pdf')

    @sanitize(
        username=StringSanitizer(required=True),
        printjob=StringSanitizer(required=True),
    )
    @simple_response
    def delete(self, username, printjob):
        """Delete a print job

		requests.options = {}
		'username' -- owner of the print job
		'printjob' -- relative filename of the print job

		return: <PDF document>
		"""
        path = self._get_path(username, printjob)

        success = True
        if os.path.exists(path):
            MODULE.info('Deleting print job %r' % (path, ))
            try:
                os.unlink(path)
            except OSError as exc:
                success = False
                MODULE.error('Error deleting print job: %s' % (exc, ))
        return success

    def pw_callback(self, prompt):
        if self.pw_callback_bad_password:
            return None
        else:
            self.pw_callback_bad_password = True
            return self.password

    @require_password
    @sanitize(
        username=StringSanitizer(required=True),
        printjob=StringSanitizer(required=True),
        printer=StringSanitizer(required=True),
    )
    @simple_response
    def printit(self, username, printjob, printer):
        """Print a given document on the given printer

		requests.options = {}
		'username' -- owner of the print job
		'printjob' -- relative filename of the print job
		'printer' -- the printer to use (<hostname>://<printer>)

		return: <PDF document>
		"""

        path = self._get_path(username, printjob)

        try:
            spoolhost, printer = printer.split('://', 1)
        except ValueError:
            raise UMC_Error(_('Invalid printer URI'))

        if not os.path.exists(path):
            raise UMC_Error(
                _('File %r could not be printed as it does not exists (anymore).'
                  ) % (printjob, ))

        MODULE.process('Printing: %s' % path)
        self.pw_callback_bad_password = False
        try:
            cups.setUser(self.username)
            cups.setEncryption(cups.HTTP_ENCRYPT_ALWAYS)
            cups.setPasswordCB(self.pw_callback)
            conn = cups.Connection(spoolhost)
            conn.printFile(printer, path, Printjob.filename2label(printjob),
                           {})
        except RuntimeError:
            raise UMC_Error(
                _('Failed to connect to print server %(printserver)s.') %
                {'printserver': spoolhost})
        except cups.IPPError as (errno, description):
            IPP_AUTHENTICATION_CANCELED = 4096
            description = {
                cups.IPP_NOT_AUTHORIZED: _('No permission to print'),
                IPP_AUTHENTICATION_CANCELED: _('Wrong password'),
            }.get(errno, description)
            raise UMC_Error(
                _('Failed to print on %(printer)s: %(stderr)s (error %(errno)d).'
                  ) % {
                      'printer': printer,
                      'stderr': description,
                      'errno': errno
                  })

        # delete file
        MODULE.info('Deleting print job %r' % (path, ))
        os.remove(path)

        return True
Beispiel #7
0
class Instance(SchoolBaseModule):
    def query(self, request):
        """Searches for internet filter rules
		requests.options = {}
		'pattern' -- pattern to match within the rule name or the list of domains
		"""
        MODULE.info('internetrules.query: options: %s' % str(request.options))
        pattern = request.options.get('pattern', '').lower()

        def _matchDomain(domains):
            # helper function to match pattern within the list of domains
            matches = [idom for idom in domains if pattern in idom.lower()]
            return 0 < len(matches)

        # filter out all rules that match the given pattern
        result = [
            dict(
                name=irule.name,
                type=_filterTypesInv[irule.type],
                domains=len(irule.domains),
                priority=irule.priority,
                wlan=irule.wlan,
            ) for irule in rules.list()
            if pattern in irule.name.lower() or _matchDomain(irule.domains)
        ]

        MODULE.info('internetrules.query: results: %s' % str(result))
        self.finished(request.id, result)

    @sanitize(StringSanitizer())
    def get(self, request):
        """Returns the specified rules
		requests.options = [ <ruleName>, ... ]
		"""
        MODULE.info('internetrules.get: options: %s' % str(request.options))
        result = []
        # fetch all rules with the given names (we need to make sure that "name" is UTF8)
        names = set(iname.encode('utf8') for iname in request.options)
        result = [
            dict(
                name=irule.name,
                type=_filterTypesInv[irule.type],
                domains=irule.domains,
                priority=irule.priority,
                wlan=irule.wlan,
            ) for irule in rules.list() if irule.name in names
        ]

        MODULE.info('internetrules.get: results: %s' % str(result))
        self.finished(request.id, result)

    @sanitize(DictSanitizer(dict(object=StringSanitizer()), required=True))
    def remove(self, request):
        """Removes the specified rules
		requests.options = [ { "object": <ruleName> }, ... ]
		"""
        MODULE.info('internetrules.remove: options: %s' % str(request.options))
        result = []
        # fetch all rules with the given names
        for ientry in request.options:
            iname = ientry['object']
            success = False
            if iname:
                success = rules.remove(iname)
            result.append(dict(name=iname, success=success))

        MODULE.info('internetrules.remove: results: %s' % str(result))
        self.finished(request.id, result)

    @staticmethod
    def _parseRule(iprops, forceAllProperties=False):
        # validate types
        for ikey, itype in (('name', basestring), ('type', basestring),
                            ('priority', (int, basestring)), ('wlan', bool),
                            ('domains', list)):
            if ikey not in iprops:
                if forceAllProperties:
                    # raise exception as the key is not present
                    raise ValueError(
                        _('The key "%s" has not been specified: %s') %
                        (ikey, iprops))
                continue
            if not isinstance(iprops[ikey], itype):
                typeStr = ''
                if isinstance(itype, tuple):
                    typeStr = ', '.join([i.__name__ for i in itype])
                else:
                    typeStr = itype.__name__
                raise ValueError(
                    _('The key "%s" needs to be of type: %s') %
                    (ikey, typeStr))

        # validate name
        if 'name' in iprops and not univention.config_registry.validate_key(
                iprops['name'].encode('utf-8')):
            raise ValueError(
                _('Invalid rule name "%s". The name needs to be a string, the following special characters are not allowed: %s'
                  ) %
                (iprops.get('name'),
                 '!, ", §, $, %, &, (, ), [, ], {, }, =, ?, `, +, #, \', ",", ;, <, >, \\'
                 ))

        # validate type
        if 'type' in iprops and iprops['type'] not in _filterTypes:
            raise ValueError(_('Filter type is unknown: %s') % iprops['type'])

        # validate domains
        if 'domains' in iprops:
            parsedDomains = []
            for idomain in iprops['domains']:

                def _validValueChar():
                    # helper function to check for invalid characters
                    for ichar in idomain:
                        if ichar in univention.config_registry.backend.INVALID_VALUE_CHARS:
                            return False
                    return True

                if not isinstance(idomain,
                                  basestring) or not _validValueChar():
                    raise ValueError(_('Invalid domain '))

                # parse domain
                domain = idomain
                if '://' not in domain:
                    # make sure that we have a scheme defined for parsing
                    MODULE.info(
                        'Adding a leading scheme for parsing of domain: %s' %
                        idomain)
                    domain = 'http://%s' % domain
                domain = urlparse(domain).hostname
                MODULE.info('Parsed domain: %s -> %s' % (idomain, domain))
                if not domain:
                    raise ValueError(
                        _('The specified domain "%s" is not valid. Please specify a valid domain name, such as "wikipedia.org", "facebook.com"'
                          ) % idomain)

                # add domain to list of parsed domains
                parsedDomains.append(domain)

            # save parsed domains in the dict
            iprops['domains'] = parsedDomains

        return iprops

    @sanitize(
        DictSanitizer(dict(object=DictSanitizer(dict(
            name=StringSanitizer(required=True),
            type=ChoicesSanitizer(list(_filterTypes.keys()), required=True),
            wlan=BooleanSanitizer(required=True),
            priority=IntegerSanitizer(required=True),
            domains=ListSanitizer(StringSanitizer(required=True),
                                  required=True),
        ),
                                                required=True)),
                      required=True))
    def add(self, request):
        """Add the specified new rules:
		requests.options = [ {
			'object': {
				'name': <str>,
				'type': 'whitelist' | 'blacklist',
				'priority': <int> | <str>,
				'wlan': <bool>,
				'domains': [<str>, ...],
			}
		}, ... ]
		"""

        # try to create all specified projects
        result = []
        for ientry in request.options:
            iprops = ientry['object']
            try:

                # make sure that the rule does not already exist
                irule = rules.load(iprops['name'])
                if irule:
                    raise ValueError(
                        _('A rule with the same name does already exist: %s') %
                        iprops['name'])

                # parse the properties
                parsedProps = self._parseRule(iprops, True)

                # create a new rule from the user input
                newRule = rules.Rule(
                    name=parsedProps['name'],
                    type=_filterTypes[parsedProps['type']],
                    priority=parsedProps['priority'],
                    wlan=parsedProps['wlan'],
                    domains=parsedProps['domains'],
                )

                # try to save filter rule
                newRule.save()
                MODULE.info('Created new rule: %s' % newRule)

                # everything ok
                result.append(dict(name=iprops['name'], success=True))
            except (ValueError, KeyError) as e:
                # data not valid... create error info
                MODULE.info(
                    'data for internet filter rule "%s" is not valid: %s' %
                    (iprops.get('name'), e))
                result.append(
                    dict(name=iprops.get('name'),
                         success=False,
                         details=str(e)))

        # return the results
        self.finished(request.id, result)

    @sanitize(
        DictSanitizer(dict(
            object=DictSanitizer(dict(
                name=StringSanitizer(required=True),
                type=ChoicesSanitizer(list(_filterTypes.keys()),
                                      required=True),
                wlan=BooleanSanitizer(required=True),
                priority=IntegerSanitizer(required=True),
                domains=ListSanitizer(StringSanitizer(required=True),
                                      required=True),
            ),
                                 required=True),
            options=DictSanitizer(dict(name=StringSanitizer()), required=True),
        ),
                      required=True))
    def put(self, request):
        """Modify an existing rules:
		requests.options = [ {
			'object': {
				'name': <str>, 						# optional
				'type': 'whitelist' | 'blacklist', 	# optional
				'priority': <int>, 					# optional
				'wlan': <bool>,						# optional
				'domains': [<str>, ...],  			# optional
			},
			'options': {
				'name': <str>  # the original name of the object
			}
		}, ... ]
		"""

        # try to create all specified projects
        result = []
        for ientry in request.options:
            try:
                # get properties and options from entry
                iprops = ientry['object']
                iname = None
                ioptions = ientry.get('options')
                if ioptions:
                    iname = ioptions.get('name')
                if not iname:
                    raise ValueError(
                        _('No "name" attribute has been specified in the options.'
                          ))

                # make sure that the rule already exists
                irule = rules.load(iname)
                if not irule:
                    raise ValueError(
                        _('The rule does not exist and cannot be modified: %s')
                        % iprops.get('name', ''))

                # parse the properties
                self._parseRule(iprops)

                if iprops.get('name', iname) != iname:
                    # name has been changed -> remove old rule and create a new one
                    rules.remove(iname)
                    irule.name = iprops['name']

                if 'type' in iprops:
                    # set rule type, move all domains from the previous type
                    oldDomains = irule.domains
                    irule.domains = []
                    irule.type = _filterTypes[iprops['type']]
                    irule.domains = oldDomains

                if 'priority' in iprops:
                    # set priority
                    irule.priority = iprops['priority']

                if 'wlan' in iprops:
                    # set wlan
                    irule.wlan = iprops['wlan']

                if 'domains' in iprops:
                    # set domains
                    irule.domains = iprops['domains']

                # try to save filter rule
                irule.save()
                MODULE.info('Saved rule: %s' % irule)

                # everything ok
                result.append(dict(name=iname, success=True))
            except ValueError as e:
                # data not valid... create error info
                MODULE.info(
                    'data for internet filter rule "%s" is not valid: %s' %
                    (iprops.get('name'), e))
                result.append(
                    dict(name=iprops.get('name'),
                         success=False,
                         details=str(e)))

        # return the results
        self.finished(request.id, result)

    @sanitize(school=SchoolSanitizer(required=True),
              pattern=StringSanitizer(default=''))
    @LDAP_Connection()
    def groups_query(self, request, ldap_user_read=None, ldap_position=None):
        """List all groups (classes, workgroups) and their assigned internet rule"""
        pattern = LDAP_Filter.forAll(request.options.get('pattern', ''),
                                     ['name', 'description'])
        school = request.options['school']
        groups = [
            x for x in Group.get_all(ldap_user_read, school, pattern)
            if not x.self_is_computerroom()
        ]

        internet_rules = rules.getGroupRuleName([i.name for i in groups])
        name = re.compile('-%s$' % (re.escape(school)), flags=re.I)
        result = [{
            'name':
            i.get_relative_name()
            if hasattr(i, 'get_relative_name') else name.sub('', i.name),
            '$dn$':
            i.dn,
            'rule':
            internet_rules.get(i.name, 'default')
            or _('-- Default (unrestricted) --')
        } for i in groups]
        result.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()),
                    key=lambda x: x['name'])

        self.finished(request.id, result)

    @sanitize(
        DictSanitizer(
            dict(
                group=StringSanitizer(required=True),
                rule=StringSanitizer(required=True),
            )))
    @LDAP_Connection()
    def groups_assign(self, request, ldap_user_read=None, ldap_position=None):
        """Assigns default rules to groups:
		request.options = [ { 'group': <groupDN>, 'rule': <ruleName> }, ... ]
		"""
        MODULE.info('internetrules.groups_assign: options: %s' %
                    str(request.options))

        # try to load all group rules
        newRules = {}
        rmRules = []
        for ientry in request.options:
            # make sure the group exists
            igrp = udm_objects.get(udm_modules.get('groups/group'), None,
                                   ldap_user_read, ldap_position,
                                   ientry['group'])
            if not igrp:
                raise UMC_Error('unknown group object')
            igrp.open()

            # check the rule name
            irule = ientry['rule']
            if irule == '$default$':
                # remove the rule
                rmRules.append(igrp['name'])
            else:
                try:
                    # make sure the rule name is valid
                    self._parseRule(dict(name=irule))
                except ValueError as exc:
                    raise UMC_Error(str(exc))

                # add new rule
                newRules[igrp['name']] = irule

        # assign default filter rules to groups
        rules.setGroupRuleName(newRules)
        rules.unsetGroupRuleName(rmRules)

        MODULE.info('internetrules.groups_assign: finished')
        self.finished(request.id, True)
Beispiel #8
0
class Instance(SchoolBaseModule):
    @sanitize(
        school=SchoolSanitizer(required=True),
        message=StringSanitizer(required=True),
        category=StringSanitizer(required=True),
    )
    @LDAP_Connection()
    def send(self, request, ldap_user_read=None, ldap_position=None):
        ucr.load()
        if not ucr.get('ucsschool/helpdesk/recipient'):
            raise UMC_Error(_(
                'The message could not be send to the helpdesk team: The email address for the helpdesk team is not configured. It must be configured by an administrator via the UCR variable "ucsschool/helpdesk/recipient".'
            ),
                            status=500)

        def _send_thread(sender, recipients, subject, message):
            MODULE.info('sending mail: thread running')

            msg = u'From: %s\r\n' % (sanitize_header(sender), )
            msg += u'To: %s\r\n' % (sanitize_header(', '.join(recipients)), )
            msg += u'Subject: =?UTF-8?Q?%s?=\r\n' % (
                sanitize_header(subject).encode('quopri'), )
            msg += u'Content-Type: text/plain; charset="UTF-8"\r\n'
            msg += u'\r\n'
            msg += message
            msg += u'\r\n'
            msg = msg.encode('UTF-8')

            server = smtplib.SMTP('localhost')
            server.set_debuglevel(0)
            server.sendmail(sender, recipients, msg)
            server.quit()
            return True

        recipients = ucr['ucsschool/helpdesk/recipient'].split(' ')
        school = School.from_dn(
            School(name=request.options['school']).dn, None,
            ldap_user_read).display_name
        category = request.options['category']
        message = request.options['message']

        subject = u'%s (%s: %s)' % (category, _('School'), school)

        try:
            user = User(None, ldap_user_read, ldap_position, self.user_dn)
            user.open()
        except ldap.LDAPError:
            MODULE.error('Errror receiving user information: %s' %
                         (traceback.format_exception(), ))
            user = {
                'displayName': self.username,
                'mailPrimaryAddress': '',
                'mailAlternativeAddress': [],
                'e-mail': [],
                'phone': []
            }
        mails = set([user['mailPrimaryAddress']]) | set(
            user['mailAlternativeAddress']) | set(user['e-mail'])

        sender = user['mailPrimaryAddress']
        if not sender:
            if ucr.get('hostname') and ucr.get('domainname'):
                sender = 'ucsschool-helpdesk@%s.%s' % (ucr['hostname'],
                                                       ucr['domainname'])
            else:
                sender = 'ucsschool-helpdesk@localhost'

        data = [
            (_('Sender'), u'%s (%s)' % (user['displayName'], self.username)),
            (_('School'), school),
            (_('Mail address'), u', '.join(mails)),
            (_('Phone number'), u', '.join(user['phone'])),
            (_('Category'), category),
            (_('Message'), u'\r\n%s' % (message, )),
        ]
        msg = u'\r\n'.join(u'%s: %s' % (key, value) for key, value in data)

        MODULE.info(
            'sending message: %s' %
            ('\n'.join(map(lambda x: repr(x.strip()), msg.splitlines()))), )

        func = notifier.Callback(_send_thread, sender, recipients, subject,
                                 msg)
        MODULE.info('sending mail: starting thread')
        thread = notifier.threads.Simple(
            'HelpdeskMessage', func,
            notifier.Callback(self.thread_finished_callback, request))
        thread.run()

    @LDAP_Connection()
    def categories(self, request, ldap_user_read=None, ldap_position=None):
        categories = []
        res = ldap_user_read.searchDn(
            filter='objectClass=univentionUMCHelpdeskClass',
            base=ldap_position.getBase())
        # use only first object found
        if res and res[0]:
            categories = ldap_user_read.getAttr(
                res[0], 'univentionUMCHelpdeskCategory')

        self.finished(request.id,
                      map(lambda x: {
                          'id': x,
                          'label': x
                      }, categories))
Beispiel #9
0
class Instance(SchoolBaseModule):
    def init(self):
        super(Instance, self).init()
        add_module_logger_to_schoollib()

    @sanitize(school=SchoolSanitizer(required=True),
              pattern=StringSanitizer(default=''))
    @LDAP_Connection()
    def computers(self, request, ldap_user_read=None):
        pattern = LDAP_Filter.forComputers(request.options.get('pattern', ''))

        result = [{
            'label': x.name,
            'id': x.dn
        } for x in SchoolComputer.get_all(ldap_user_read,
                                          request.options['school'], pattern)]
        result = sorted(result,
                        cmp=lambda x, y: cmp(x.lower(), y.lower()),
                        key=lambda x: x['label'])  # TODO: still necessary?

        self.finished(request.id, result)

    @sanitize(school=SchoolSanitizer(required=True),
              pattern=StringSanitizer(default=''))
    @LDAP_Connection()
    def query(self, request, ldap_user_read=None):
        school = request.options['school']
        pattern = LDAP_Filter.forGroups(request.options.get('pattern', ''),
                                        school)

        result = [{
            'name': x.get_relative_name(),
            'description': x.description or '',
            '$dn$': x.dn,
        } for x in ComputerRoom.get_all(ldap_user_read, school, pattern)]
        result = sorted(result,
                        cmp=lambda x, y: cmp(x.lower(), y.lower()),
                        key=lambda x: x['name'])  # TODO: still necessary?

        self.finished(request.id, result)

    @sanitize(DNSanitizer(required=True))
    @LDAP_Connection()
    def get(self, request, ldap_user_read=None):
        # open the specified room
        room = ComputerRoom.from_dn(request.options[0], None, ldap_user_read)
        result = room.to_dict()
        result['computers'] = result.get('hosts')
        self.finished(request.id, [result])

    @sanitize(DictSanitizer(dict(object=DictSanitizer({}, required=True))))
    @LDAP_Connection(USER_READ, USER_WRITE)
    def add(self, request, ldap_user_write=None, ldap_user_read=None):
        """Adds a new room"""
        group_props = request.options[0].get('object', {})
        group_props['hosts'] = group_props.get('computers')
        room = ComputerRoom(**group_props)
        if room.get_relative_name() == room.name:
            room.name = '%(school)s-%(name)s' % group_props
            room.set_dn(room.dn)
        success = room.create(ldap_user_write)
        self.finished(request.id, [success])

    @sanitize(DictSanitizer(dict(object=DictSanitizer({}, required=True))))
    @LDAP_Connection(USER_READ, USER_WRITE)
    def put(self, request, ldap_user_write=None, ldap_user_read=None):
        """Modify an existing room"""
        group_props = request.options[0].get('object', {})
        group_props['hosts'] = group_props.get('computers')

        room = ComputerRoom(**group_props)
        if room.get_relative_name() == room.name:
            room.name = '%(school)s-%(name)s' % group_props
        room.set_dn(group_props['$dn$'])
        room.modify(ldap_user_write)

        self.finished(request.id, [True])

    @sanitize(
        DictSanitizer(
            dict(object=ListSanitizer(DNSanitizer(required=True),
                                      min_elements=1))))
    @LDAP_Connection(USER_READ, USER_WRITE)
    def remove(self, request, ldap_user_write=None, ldap_user_read=None):
        """Deletes a room"""

        try:
            room_dn = request.options[0]['object'][0]
            room = ComputerRoom.from_dn(room_dn, None, ldap_user_write)
            room.remove(ldap_user_write)
        except udm_exceptions.base as e:
            self.finished(request.id, [{'success': False, 'message': str(e)}])
            return

        self.finished(request.id, [{'success': True}])