def _read_user(self, userstr, ldap_user_read=None): match = self.USER_REGEX.match(userstr) if not match or not userstr: raise AttributeError('invalid key "%s"' % userstr) username = match.groupdict()['username'] if not username: raise AttributeError('username missing: %s' % userstr) lo = ldap_user_read try: userobj = User.get_only_udm_obj( lo, filter_format('uid=%s', (username, ))) if userobj is None: raise noObject(username) user = User.from_udm_obj(userobj, None, lo) except (noObject, MultipleObjectsError): MODULE.info('Unknown user "%s"' % username) dict.__setitem__(self, userstr, UserInfo('', '')) return blacklisted_groups = set([ x.strip().lower() for x in ucr.get( 'ucsschool/umc/computerroom/hide_screenshots/groups', 'Domain Admins').split(',') ]) users_groupmemberships = set( [explode_dn(x, True)[0].lower() for x in userobj['groups']]) MODULE.info('UserMap: %s: hide screenshots for following groups: %s' % ( username, blacklisted_groups, )) MODULE.info('UserMap: %s: user is member of following groups: %s' % ( username, users_groupmemberships, )) hide_screenshot = bool(blacklisted_groups & users_groupmemberships) if ucr.is_true('ucsschool/umc/computerroom/hide_screenshots/teachers', True) and user.is_teacher(lo): MODULE.info('UserMap: %s: is teacher hiding screenshot' % (username, )) hide_screenshot = True MODULE.info('UserMap: %s: hide_screenshot=%r' % (username, hide_screenshot)) dict.__setitem__( self, userstr, UserInfo(user.dn, username, isTeacher=user.is_teacher(lo), hide_screenshot=hide_screenshot))
def _filter_members(request, group, users, ldap_user_read=None): members = [] for member_dn in users: try: user = User.from_dn(member_dn, None, ldap_user_read) except udm_exceptions.noObject: MODULE.process( 'Could not open (foreign) user %r: no permissions/does not exists/not a user' % (member_dn, )) continue if not user.schools or not set(user.schools) & {group.school}: continue if request.flavor == 'class' and not user.is_teacher( ldap_user_read): continue # only display teachers elif request.flavor == 'workgroup' and not user.is_student( ldap_user_read): continue # only display students elif request.flavor == 'workgroup-admin' and not user.is_student( ldap_user_read) and not user.is_administrator( ldap_user_read) and not user.is_staff( ldap_user_read) and not user.is_teacher( ldap_user_read): continue # only display school users members.append({ 'id': user.dn, 'label': Display.user(user.get_udm_object(ldap_user_read)) }) return members
def 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)
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 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
def rooms(self, request, ldap_user_read=None): """Returns a list of all available rooms""" rooms = [] try: all_rooms = ComputerRoom.get_all(ldap_user_read, request.options['school']) except udm_exceptions.noObject: all_rooms = [] for room in all_rooms: room_info = _readRoomInfo(room.dn) user_dn = room_info.get('user') locked = user_dn and not compare_dn(user_dn, self.user_dn) and ( 'pid' in room_info or 'exam' in room_info) if locked: try: # open the corresponding UDM object to get a displayable user name user_dn = Display.user( User.from_dn( user_dn, None, ldap_user_read).get_udm_object(ldap_user_read)) except udm_exceptions.base as exc: MODULE.warn( 'Cannot open LDAP information for user %r: %s' % (user_dn, exc)) rooms.append({ 'id': room.dn, 'label': room.get_relative_name(), 'user': user_dn, 'locked': locked, 'exam': room_info.get('exam'), 'examDescription': room_info.get('examDescription'), 'examEndTime': room_info.get('examEndTime'), }) self.finished(request.id, rooms)
def 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)))
def put(self, request, ldap_machine_write=None, ldap_user_read=None, ldap_position=None): """Returns the objects for the given IDs requests.options = [ { object : ..., options : ... }, ... ] return: True|<error message> """ if request.flavor == 'teacher': request.options = request.options[0]['object'] return self.add_teacher_to_classes(request) klass = get_group_class(request) for group_from_umc in request.options: group_from_umc = group_from_umc['object'] group_from_umc_dn = group_from_umc['$dn$'] break try: group_from_ldap = klass.from_dn(group_from_umc_dn, None, ldap_machine_write) except udm_exceptions.noObject: raise UMC_Error('unknown group object') old_members = self._filter_members(request, group_from_ldap, group_from_ldap.users, ldap_user_read) removed_members = set(o['id'] for o in old_members) - set( group_from_umc['members']) MODULE.info('Modifying group "%s" with members: %s' % (group_from_ldap.dn, group_from_ldap.users)) MODULE.info('New members: %s' % group_from_umc['members']) MODULE.info('Removed members: %s' % (removed_members, )) if request.flavor == 'workgroup-admin': # do not allow groups to be renamed in order to avoid conflicts with shares # grp.name = '%(school)s-%(name)s' % group group_from_ldap.description = group_from_umc['description'] # Workgroup admin view → update teachers, admins, students, (staff) # Class view → update only the group's teachers (keep all non teachers) # Workgroup teacher view → update only the group's students users = [] # keep specific users from the group for userdn in group_from_ldap.users: try: user = User.from_dn(userdn, None, ldap_machine_write) except udm_exceptions.noObject: # no permissions/is not a user/does not exists → keep the old value users.append(userdn) continue if not user.schools or not set(user.schools) & set( [group_from_ldap.school]): users.append(userdn) continue if (request.flavor == 'class' and not user.is_teacher(ldap_machine_write)) or ( request.flavor == 'workgroup' and not user.is_student(ldap_machine_write) ) or request.flavor == 'workgroup-admin': users.append(userdn) # add only certain users to the group for userdn in group_from_umc['members']: try: user = User.from_dn(userdn, None, ldap_machine_write) except udm_exceptions.noObject as exc: MODULE.error('Not adding not existing user %r to group: %r.' % (userdn, exc)) continue if not user.schools or not set(user.schools) & set( [group_from_ldap.school]): raise UMC_Error( _('User %s does not belong to school %r.') % (Display.user(user.get_udm_object(ldap_machine_write)), group_from_ldap.school)) if request.flavor == 'workgroup-admin' and not user.is_student( ldap_machine_write) and not user.is_administrator( ldap_machine_write) and not user.is_staff( ldap_machine_write) and not user.is_teacher( ldap_machine_write): raise UMC_Error( _('User %s does not belong to school %r.') % (Display.user(user.get_udm_object(ldap_machine_write)), group_from_ldap.school)) if request.flavor == 'class' and not user.is_teacher( ldap_machine_write): raise UMC_Error( _('User %s is not a teacher.') % (Display.user(user.get_udm_object(ldap_machine_write)), )) if request.flavor == 'workgroup' and not user.is_student( ldap_machine_write): raise UMC_Error( _('User %s is not a student.') % (Display.user(user.get_udm_object(ldap_machine_write)), )) users.append(user.dn) group_from_ldap.users = list(set(users) - removed_members) try: success = group_from_ldap.modify(ldap_machine_write) MODULE.info('Modified, group has now members: %s' % (group_from_ldap.users, )) except udm_exceptions.base as exc: MODULE.process('An error occurred while modifying "%s": %s' % (group_from_umc['$dn$'], exc.message)) raise UMC_Error(_('Failed to modify group (%s).') % exc.message) self.finished(request.id, success)
def create_user( self, ou_name, schools=None, username=None, firstname=None, lastname=None, classes=None, mailaddress=None, is_teacher=False, is_staff=False, is_active=True, password='******', use_cli=False, wait_for_replication=True ): """ Create a user in specified OU with given attributes. If attributes are not specified, random values will be used for username, firstname and lastname. If password is not None, the given password will be set for this user. Return value: (user_name, user_dn) user_name: name of the created user user_dn: DN of the created user object """ if not ou_name: raise SchoolMissingOU('No OU name specified') # set default values if username is None: username = uts.random_username() if firstname is None: firstname = uts.random_string(length=10, numeric=False) if lastname is None: lastname = uts.random_string(length=10, numeric=False) if mailaddress is None: mailaddress = '' if schools is None: schools = [ou_name] user_dn = 'uid=%s,%s' % (username, self.get_user_container(ou_name, is_teacher, is_staff)) if use_cli: if classes is None: classes = '' if classes: if not all(["-" in c for c in classes.split(',')]): utils.fail('*** Class names must be <school-ou>-<class-name>.') # create import file line = 'A\t%s\t%s\t%s\t%s\t%s\t\t%s\t%d\t%d\t%d\n' % (username, lastname, firstname, ou_name, classes, mailaddress, int(is_teacher), int(is_active), int(is_staff)) with tempfile.NamedTemporaryFile() as tmp_file: tmp_file.write(line) tmp_file.flush() cmd = [self.PATH_CMD_IMPORT_USER, tmp_file.name] print '*** Calling following command: %r' % cmd retval = subprocess.call(cmd) if retval: utils.fail('create_ou failed with exitcode %s' % retval) if password is not None: self._set_password(user_dn, password) else: school_classes = defaultdict(list) if classes: for kls in classes.split(','): school_classes[kls.partition('-')[0]].append(kls) kwargs = { 'school': ou_name, 'schools': schools, 'name': username, 'firstname': firstname, 'lastname': lastname, 'email': mailaddress, 'password': password, 'disabled': not is_active, "school_classes": dict(school_classes) } print '*** Creating new user %r with %r.' % (username, kwargs) lo = self.open_ldap_connection() User.invalidate_all_caches() User.init_udm_module(lo) # TODO FIXME has to be fixed in ucs-school-lib - should be done automatically cls = Student if is_teacher and is_staff: cls = TeachersAndStaff elif is_teacher and not is_staff: cls = Teacher elif not is_teacher and is_staff: cls = Staff result = cls(**kwargs).create(lo) print '*** Result of %s(...).create(): %r' % (cls.__name__, result,) if wait_for_replication: utils.wait_for_replication() return username, 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)