class AccountsController(BaseController): "Accounts controller" def __before__(self): "set context" BaseController.__before__(self) if self.identity: c.user = self.identity['user'] else: c.user = None c.selectedtab = 'accounts' def login(self): "login" if request.remote_addr in session: if session[request.remote_addr] > arrow.utcnow().datetime: msg = _('You have been banned after' ' several failed logins') log.info(msg) abort(409, msg) else: del session[request.remote_addr] session.save() identity = request.environ.get('repoze.who.identity') came_from = unquote(str(request.GET.get('came_from', ''))) if not came_from or ' ' in came_from: came_from = url('home') if '://' in came_from: from_url = urlparse(came_from) came_from = from_url[2] if identity: redirect(url(came_from)) else: c.came_from = came_from c.login_counter = request.environ['repoze.who.logins'] if c.login_counter >= 3: ban_until = arrow.utcnow().datetime + timedelta(minutes=5) if request.remote_addr not in session: session[request.remote_addr] = ban_until session.save() else: if arrow.utcnow().datetime > session[request.remote_addr]: del session[request.remote_addr] session.save() c.form = ResetPwForm(request.POST, csrf_context=session) return self.render('/accounts/login.html') def loggedin(self): "Landing page" came_from = (unquote(str(request.params.get('came_from', ''))) or url('/')) if not self.identity: if 'repoze.who.logins' in request.environ: login_counter = request.environ['repoze.who.logins'] + 1 else: abort(409) redirect( url('/accounts/login', came_from=came_from, __logins=login_counter)) userid = self.identity['repoze.who.userid'] user = self.identity['user'] if user is None: try: user, local_part, domain, domains = add_user(userid) msg = _('First time Login from external auth,' ' your local account was created') user_address_update(user, local_part, domain, domains, self.identity) except IntegrityError: Session.rollback() redirect(url('/logout')) except ldap.LDAPError: pass else: if not user.active: redirect(url('/logout')) msg = _('Login successful, Welcome back %(username)s !' % dict(username=userid)) update_login(user) if user.is_peleb: for domain in user.domains: if check_language(domain.language): session['lang'] = domain.language session.save() break session['taskids'] = [] session.save() info = auditmsgs.ACCOUNTLOGIN_MSG % dict(u=user.username) audit_log(user.username, 6, unicode(info), request.host, request.remote_addr, arrow.utcnow().datetime) flash(msg) log.info(msg) redirect(url(came_from)) # pylint: disable-msg=R0201 def loggedout(self): "Logged out page" session.clear() if 'theme' in session: del session['theme'] session.save() came_from = (unquote(str(request.params.get('came_from', ''))) or url('/accounts/login')) redirect(url(came_from)) def passwdreset(self): """Render password reset page""" c.came_from = '/' c.login_counter = 0 c.form = ResetPwForm(request.POST, csrf_context=session) if request.method == 'POST' and c.form.validate(): key_seed = '%s%s' % (c.form.email.data, arrow.utcnow().ctime()) token = hashlib.sha1(key_seed).hexdigest() user = Session.query(User)\ .filter(User.email == c.form.email.data)\ .one() if not user.local: flash( _('The account %s is an external account, use your' ' External systems to change the password. ' 'Contact your system adminstrator if you do not ' 'know which external systems you authenticate to') % user.email) redirect(url('/accounts/login')) rtoken = Session\ .query(ResetToken.used)\ .filter(ResetToken.used == false())\ .filter(ResetToken.user_id == user.id)\ .all() if not rtoken: rtoken = ResetToken(token, user.id) Session.add(rtoken) Session.commit() host = URL_PREFIX_RE.sub('', request.host_url) c.username = user.username c.firstname = user.firstname or user.username c.reset_url = url('accounts-pw-token-reset', token=token, host=host) text = self.render('/email/pwreset.txt') mailer = Mailer(get_conf_options(config)) mailer.start() sdrnme = config.get('baruwa.custom.name', 'Baruwa') email = Msg(author=[(sdrnme, config.get('baruwa.reports.sender'))], to=[('', c.form.email.data)], subject=_("[%s] Password reset request") % sdrnme) email.plain = text mailer.send(email) mailer.stop() flash( _('An email has been sent to the address provided, ' 'please follow the instructions in that email to ' 'reset your password.')) redirect(url('/accounts/login')) return self.render('/accounts/login.html') def pwtokenreset(self, token): """Reset password using token""" try: token = Session.query(ResetToken)\ .filter(ResetToken.token == token)\ .filter(ResetToken.used == false()).one() threshold = token.timestamp + timedelta(minutes=20) if arrow.utcnow().datetime > threshold: Session.delete(token) Session.commit() raise NoResultFound user = self._get_user(token.user_id) if not user or user.is_superadmin: raise NoResultFound passwd = mkpasswd() user.set_password(passwd) Session.add(user) Session.delete(token) Session.commit() c.passwd = passwd c.firstname = user.firstname or user.username text = self.render('/email/pwchanged.txt') mailer = Mailer(get_conf_options(config)) mailer.start() sdrnme = config.get('baruwa.custom.name', 'Baruwa') email = Msg(author=[(sdrnme, config.get('baruwa.reports.sender'))], to=[('', user.email)], subject=_("[%s] Password reset") % sdrnme) email.plain = text mailer.send(email) mailer.stop() flash( _('The password has been reset, check your email for' ' the temporary password you should use to login.')) except NoResultFound: msg = _('The token used is invalid or does not exist') flash_alert(msg) log.info(msg) redirect(url('/accounts/login')) @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def pwchange(self, userid): """Reset a user password""" user = self._get_user(userid) if not user: abort(404) c.form = ChangePasswordForm(request.POST, csrf_context=session) if request.method == 'POST' and c.form.validate(): if user.local and not user.is_superadmin: change_user_pw(user, c.user, c.form.password1.data, request.host, request.remote_addr) msg = _('The account password for %(name)s has been reset') \ % dict(name=user.username) flash(msg) else: if user.is_superadmin: flash(_('Admin accounts can not be modified via the web')) else: flash( _('This is an external account, use' ' external system to reset the password')) redirect(url('account-detail', userid=user.id)) c.id = userid c.username = user.username c.posturl = 'accounts-pw-change' return self.render('/accounts/pwchange.html') @ActionProtector(not_anonymous()) def upwchange(self, userid): """User change own password""" user = self._get_user(userid) if not user: abort(404) if user.id != c.user.id or c.user.is_superadmin: abort(403) c.form = UserPasswordForm(request.POST, csrf_context=session) if (request.method == 'POST' and c.form.validate() and user.validate_password(c.form.password3.data)): if user.local: user.set_password(c.form.password1.data) Session.add(user) Session.commit() flash( _('The account password for %(name)s has been reset') % dict(name=user.username)) info = auditmsgs.PASSWORDCHANGE_MSG % dict(u=user.username) audit_log(c.user.username, 2, unicode(info), request.host, request.remote_addr, arrow.utcnow().datetime) else: flash( _('This is an external account, use' ' external system to reset the password')) redirect(url('account-detail', userid=user.id)) elif (request.method == 'POST' and not user.validate_password(c.form.password3.data) and not c.form.password3.errors): flash_alert( _('The old password supplied does' ' not match our records')) c.id = userid c.username = user.username c.posturl = 'accounts-pw-uchange' return self.render('/accounts/pwchange.html') # pylint: disable-msg=W0622 @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def index(self, page=1, orgid=None, domid=None, format=None): """GET /accounts/: Paginate items in the collection""" num_items = session.get('accounts_num_items', 10) c.form = BulkDelUsers(request.POST, csrf_context=session) if request.method == 'POST': if c.form.accountid.data and \ str(c.user.id) in c.form.accountid.data: c.form.accountid.data.remove(str(c.user.id)) if c.form.accountid.data and c.form.whatdo.data == 'disable': Session.query(User)\ .filter(User.id.in_(c.form.accountid.data))\ .update({'active': False}, synchronize_session='fetch') Session.commit() if c.form.accountid.data and c.form.whatdo.data == 'enable': Session.query(User)\ .filter(User.id.in_(c.form.accountid.data))\ .update({'active': True}, synchronize_session='fetch') Session.commit() if c.form.accountid.data and c.form.whatdo.data == 'delete': session['bulk_account_delete'] = c.form.accountid.data session.save() # redirect for confirmation redirect(url('accounts-confirm-delete')) users = Session.query(User.id, User.username, User.firstname, User.lastname, User.email, User.active, User.local, User.account_type).order_by(User.id) usrcount = Session.query(User.id) if c.user.is_domain_admin: users = users.join(domain_users, (dom_owns, domain_users.c.domain_id == dom_owns.c.domain_id), (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) usrcount = usrcount.join(domain_users, (dom_owns, domain_users.c.domain_id == dom_owns.c.domain_id), (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) if domid: users = users.filter( and_(domain_users.c.domain_id == domid, domain_users.c.user_id == User.id)) usrcount = usrcount.filter( and_(domain_users.c.domain_id == domid, domain_users.c.user_id == User.id)) if orgid: users = users.filter( and_( domain_users.c.user_id == User.id, domain_users.c.domain_id == dom_owns.c.domain_id, dom_owns.c.organization_id == orgid, )) usrcount = usrcount.filter( and_( domain_users.c.user_id == User.id, domain_users.c.domain_id == dom_owns.c.domain_id, dom_owns.c.organization_id == orgid, )) users = users.distinct(User.id) usrcount = usrcount.distinct(User.id) pages = paginate.Page(users, page=int(page), items_per_page=num_items, item_count=usrcount.count()) if format == 'json': response.headers['Content-Type'] = 'application/json' data = convert_acct_to_json(pages, orgid) return data c.page = pages c.domid = domid c.orgid = orgid return self.render('/accounts/index.html') # pylint: disable-msg=R0914,W0142,W0613 @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def search(self, format=None): "Search for accounts" total_found = 0 search_time = 0 num_items = session.get('accounts_num_items', 10) qry = request.GET.get('q', '') doms = request.GET.get('d', None) kwds = {'presliced_list': True} page = int(request.GET.get('p', 1)) conn = SphinxClient() sphinxopts = extract_sphinx_opts(config['sphinx.url']) conn.SetServer(sphinxopts.get('host', '127.0.0.1')) conn.SetMatchMode(SPH_MATCH_EXTENDED2) conn.SetFieldWeights( dict(username=50, email=30, firstname=10, lastname=10)) if page == 1: conn.SetLimits(0, num_items, 500) else: page = int(page) offset = (page - 1) * num_items conn.SetLimits(offset, num_items, 500) if doms: conn.SetFilter('domains', [ int(doms), ]) if c.user.is_domain_admin: # crcs = get_dom_crcs(Session, c.user) domains = Session.query(Domain.id).join(dom_owns, (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) conn.SetFilter('domains', [domain[0] for domain in domains]) qry = clean_sphinx_q(qry) try: results = conn.Query(qry, 'accounts, accounts_rt') except (socket.timeout, struct.error): redirect(request.path_qs) qry = restore_sphinx_q(qry) if results and results['matches']: ids = [hit['id'] for hit in results['matches']] total_found = results['total_found'] search_time = results['time'] users = Session.query(User.id, User.username, User.firstname, User.lastname, User.email, User.active, User.local, User.account_type)\ .filter(User.id.in_(ids))\ .order_by(User.id)\ .all() usercount = total_found else: users = [] usercount = 0 c.q = qry c.d = doms c.total_found = total_found c.search_time = search_time c.page = paginate.Page(users, page=int(page), items_per_page=num_items, item_count=usercount, **kwds) return self.render('/accounts/searchresults.html') @ActionProtector(All(not_anonymous(), CanAccessAccount())) def detail(self, userid): """GET /accounts/userid/ Show a specific item""" user = self._get_user(userid) if not user: abort(404) c.account = user return self.render('/accounts/account.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def add(self): """/accounts/new""" c.form = user_add_form(c.user, request.POST, session) if request.method == 'POST' and c.form.validate(): try: user = create_user(c.form, c.user.username, request.host, request.remote_addr) flash( _('The account: %(user)s was created successfully') % {'user': user.username}) redirect(url('account-detail', userid=user.id)) except IntegrityError: Session.rollback() flash_alert( _('Either the username or email address already exist')) return self.render('/accounts/new.html') @ActionProtector(All(not_anonymous(), CanAccessAccount())) def edit(self, userid): """GET /accounts/edit/id: Form to edit an existing item""" user = self._get_user(userid) if not user: abort(404) c.form = user_update_form(user, c.user, request.POST, session) if request.method == 'POST' and c.form.validate(): kwd = dict(userid=user.id) if update_changed(c.form, FORM_FIELDS, user): try: update_user(user, c.user, request.host, request.remote_addr) flash(_('The account has been updated')) kwd['uc'] = 1 except IntegrityError: Session.rollback() flash_alert( _('The account: %(acc)s could not be updated') % dict(acc=user.username)) if (user.id == c.user.id and c.form.active and c.form.active.data is False): redirect(url('/logout')) else: flash_info(_('No changes made to the account')) redirect(url(controller='accounts', action='detail', **kwd)) c.fields = FORM_FIELDS c.id = userid return self.render('/accounts/edit.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def delete(self, userid): """/accounts/delete/id""" user = self._get_user(userid) if not user: abort(404) c.form = EditUserForm(request.POST, user, csrf_context=session) del c.form.domains if request.method == 'POST': if c.form.validate(): delete_user(user, c.user, request.host, request.remote_addr) flash(_('The account has been deleted')) redirect(url(controller='accounts', action='index')) else: flash_info( _('The account: %(a)s and all associated data' ' will be deleted, This action is not reversible.') % dict(a=user.username)) c.fields = FORM_FIELDS c.id = userid return self.render('/accounts/delete.html') @ActionProtector(not_anonymous()) def confirm_delete(self): "Confirm mass delete" accountids = session.get('bulk_account_delete', []) if not accountids: redirect(url(controller='accounts', action='index')) num_items = 10 if len(accountids) > num_items and len(accountids) <= 20: num_items = 20 if len(accountids) > num_items and len(accountids) <= 50: num_items = 50 if len(accountids) > num_items and len(accountids) <= 100: num_items = 100 users = Session.query(User).filter(User.id.in_(accountids)) usrcount = Session.query(User.id) if c.user.is_domain_admin and usrcount: users = users.join(domain_users, (dom_owns, domain_users.c.domain_id == dom_owns.c.domain_id), (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) usrcount = usrcount.join(domain_users, (dom_owns, domain_users.c.domain_id == dom_owns.c.domain_id), (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) if request.method == 'POST': tasks = [] # try: for account in users.all(): info = auditmsgs.DELETEACCOUNT_MSG % dict(u=account.username) Session.delete(account) tasks.append([ c.user.username, 4, unicode(info), request.host, request.remote_addr, arrow.utcnow().datetime ]) Session.commit() del session['bulk_account_delete'] session.save() backend_user_update(None, True) for task in tasks: audit_log(*task) flash(_('The accounts have been deleted')) redirect(url(controller='accounts')) else: flash( _('The following accounts are about to be deleted,' ' this action is not reversible, Do you wish to ' 'continue ?')) try: c.page = paginate.Page(users, page=1, items_per_page=num_items, item_count=usrcount.count()) except DataError: msg = _('An error occured try again') flash_alert(msg) log.info(msg) redirect(url(controller='accounts', action='index')) return self.render('/accounts/confirmbulkdel.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def addaddress(self, userid): "Add address" user = self._get_user(userid) if not user: abort(404) c.form = AddressForm(request.POST, csrf_context=session) if request.method == 'POST' and c.form.validate(): try: addr = create_address(c.form, user, c.user, request.host, request.remote_addr) flash( _('The alias address %(address)s was successfully created.' % dict(address=addr.address))) except IntegrityError: Session.rollback() msg = _('The address %(addr)s already exists') % \ dict(addr=c.form.address.data) flash_alert(msg) log.info(msg) except NoResultFound: domain = c.form.address.data.split('@')[1] msg = _('Domain: %(d)s does not belong to you') % \ dict(d=domain) flash(msg) log.info(msg) redirect(url(controller='accounts', action='detail', userid=userid)) c.id = userid return self.render('/accounts/addaddress.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def editaddress(self, addressid): "Edit address" address = get_address(addressid) if not address: abort(404) c.form = AddressForm(request.POST, address, csrf_context=session) if request.method == 'POST' and c.form.validate(): try: if (address.address != c.form.address.data or address.enabled != c.form.enabled.data): update_address(c.form, address, c.user, request.host, request.remote_addr) flash(_('The alias address has been updated')) else: flash_info(_('No changes were made to the address')) except IntegrityError: Session.rollback() msg = _('The address %(addr)s already exists') % \ dict(addr=c.form.address.data) flash_alert(msg) log.info(msg) except NoResultFound: domain = c.form.address.data.split('@')[1] msg = _('Domain: %(d)s does not belong to you') % \ dict(d=domain) flash(msg) log.info(msg) redirect( url(controller='accounts', action='detail', userid=address.user_id)) c.id = addressid c.userid = address.user_id return self.render('/accounts/editaddress.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def deleteaddress(self, addressid): "Delete address" address = get_address(addressid) if not address: abort(404) c.form = AddressForm(request.POST, address, csrf_context=session) if request.method == 'POST' and c.form.validate(): user_id = delete_address(address, c.user, request.host, request.remote_addr) flash(_('The address has been deleted')) redirect( url(controller='accounts', action='detail', userid=user_id)) c.id = addressid c.userid = address.user_id return self.render('/accounts/deleteaddress.html') def set_language(self): "Set the language" nextpage = request.params.get('next', None) if not nextpage: nextpage = request.headers.get('Referer', None) if not nextpage: nextpage = '/' if '://' in nextpage: from_url = urlparse(nextpage) nextpage = from_url[2] lang_code = request.params.get('language', None) if lang_code and check_language(lang_code): session['lang'] = lang_code session.save() params = [] for param in request.params: if param not in ['language', 'amp']: value = request.params[param] if value: if (param == 'came_from' and '://' in urllib2.unquote(value)): urlparts = urlparse(urllib2.unquote(value)) value = urlparts[2] or '/' params.append('%s=%s' % (urllib2.quote(param), urllib2.quote(value))) if 'lc=1' not in params: params.append('lc=1') if params: nextpage = "%s?%s" % (nextpage, '&'.join(params)) redirect(nextpage) @ActionProtector(All(not_anonymous(), OwnsDomain())) def import_accounts(self, domainid): "import accounts" try: cachekey = u'domain-%s' % domainid domain = Session.query(Domain.id, Domain.name)\ .filter(Domain.id == domainid)\ .options(FromCache('sql_cache_med', cachekey)).one() except NoResultFound: abort(404) c.form = ImportCSVForm(request.POST, csrf_context=session) if request.method == 'POST' and c.form.validate(): basedir = config['pylons.cache_dir'] csvdata = request.POST['csvfile'] if hasattr(csvdata, 'filename'): dstfile = os.path.join(basedir, 'uploads', csvdata.filename.lstrip(os.sep)) if not os.path.exists(dstfile) and iscsv(csvdata.file): csvfile = open(dstfile, 'w') shutil.copyfileobj(csvdata.file, csvfile) csvdata.file.close() csvfile.close() task = importaccounts.apply_async(args=[ domainid, dstfile, c.form.skipfirst.data, c.user.id ]) session['taskids'].append(task.task_id) session['acimport-count'] = 1 session['acimport-file'] = dstfile session.save() msg = _('File uploaded, and is being processed, this page' ' will automatically refresh to show the status') flash(msg) log.info(msg) redirect(url('accounts-import-status', taskid=task.task_id)) else: filename = csvdata.filename.lstrip(os.sep) if not iscsv(csvdata.file): msg = _('The file: %s is not a CSV file') % filename flash_alert(msg) log.info(msg) else: msg = _('The file: %s already exists and' ' is being processed.') % filename flash_alert(msg) log.info(msg) csvdata.file.close() else: msg = _('No CSV was file uploaded, try again') flash_alert(msg) log.info(msg) c.domain = domain return self.render('/accounts/importaccounts.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def import_status(self, taskid): "import status" result = AsyncResult(taskid) if result is None or taskid not in session['taskids']: msg = _('The task status requested has expired or does not exist') flash(msg) log.info(msg) redirect(url(controller='accounts', action='index')) if result.ready(): finished = True flash.pop_messages() if isinstance(result.result, Exception): if c.user.is_superadmin: msg = _('Error occured in processing %s') % result.result flash_alert(msg) log.info(msg) else: flash_alert(_('Backend error occured during processing.')) redirect(url(controller='accounts')) backend_user_update(None, True) audit_log(c.user.username, 3, unicode(auditmsgs.ACCOUNTIMPORT_MSG), request.host, request.remote_addr, arrow.utcnow().datetime) else: session['acimport-count'] += 1 if (session['acimport-count'] >= 10 and result.state in ['PENDING', 'RETRY', 'FAILURE']): result.revoke() try: os.unlink(session['acimport-file']) except OSError: pass del session['acimport-count'] session.save() msg = _('The import could not be processed,' ' try again later') flash_alert(msg) log.info(msg) redirect(url(controller='accounts')) finished = False c.finished = finished c.results = result.result c.success = result.successful() return self.render('/accounts/importstatus.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def export_accounts(self, domainid=None, orgid=None): "export domains" task = exportaccounts.apply_async(args=[domainid, c.user.id, orgid]) if 'taskids' not in session: session['taskids'] = [] session['taskids'].append(task.task_id) session['acexport-count'] = 1 session.save() msg = _('Accounts export is being processed') flash(msg) log.info(msg) redirect(url('accounts-export-status', taskid=task.task_id)) @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def export_status(self, taskid): "export status" result = AsyncResult(taskid) if result is None or taskid not in session['taskids']: msg = _('The task status requested has expired or does not exist') flash(msg) log.info(msg) redirect(url(controller='accounts', action='index')) if result.ready(): finished = True flash.pop_messages() if isinstance(result.result, Exception): msg = _('Error occured in processing %s') % result.result if c.user.is_superadmin: flash_alert(msg) log.info(msg) else: flash_alert(_('Backend error occured during processing.')) log.info(msg) redirect(url(controller='accounts', action='index')) results = dict( f=True if not result.result['global_error'] else False, id=taskid, global_error=result.result['global_error']) audit_log(c.user.username, 5, unicode(auditmsgs.ACCOUNTEXPORT_MSG), request.host, request.remote_addr, arrow.utcnow().datetime) else: try: session['acexport-count'] += 1 except KeyError: session['acexport-count'] = 1 session.save() if (session['acexport-count'] >= 10 and result.state in ['PENDING', 'RETRY', 'FAILURE']): result.revoke() del session['acexport-count'] session.save() msg = _('The export could not be processed, try again later') flash_alert(msg) log.info(msg) redirect(url(controller='accounts', action='index')) finished = False results = dict(f=None, global_error=None) c.finished = finished c.results = results c.success = result.successful() dwn = request.GET.get('d', None) if finished and (dwn and dwn == 'y'): response.content_type = 'text/csv' response.headers['Cache-Control'] = 'max-age=0' csvdata = result.result['f'] disposition = 'attachment; filename=accounts-export-%s.csv' % \ taskid response.headers['Content-Disposition'] = str(disposition) response.headers['Content-Length'] = len(csvdata) return csvdata return self.render('/accounts/exportstatus.html') # pylint: disable-msg=R0201,W0613 @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def setnum(self, format=None): "Set number of account items returned" num = check_num_param(request) if num and num in [10, 20, 50, 100]: session['accounts_num_items'] = num session.save() nextpage = request.headers.get('Referer', '/') if '://' in nextpage: from_url = urlparse(nextpage) nextpage = from_url[2] redirect(nextpage)
from baruwa.model.meta import Session from baruwa.model.auth import LDAPSettings, RadiusSettings from baruwa.model.accounts import Group, domain_owners from baruwa.model.accounts import organizations_admins as oa from baruwa.model.domains import DomainAlias from baruwa.model.domains import Domain, DeliveryServer, AuthServer from baruwa.forms.domains import BulkDelDomains, AddLDAPSettingsForm from baruwa.forms.domains import AddDomainForm, AddDeliveryServerForm from baruwa.forms.domains import AddAuthForm, AUTH_PROTOCOLS, EditDomainAlias from baruwa.forms.domains import AddDomainAlias, AddRadiusSettingsForm from baruwa.lib.audit.msgs.domains import * log = logging.getLogger(__name__) @ControllerProtector(All(not_anonymous(), OnlyAdminUsers())) class DomainsController(BaseController): def __before__(self): "set context" BaseController.__before__(self) if self.identity: c.user = self.identity['user'] else: c.user = None c.selectedtab = 'domains' def _get_server(self, destinationid): "utility" try: cachekey = u'deliveryserver-%s' % destinationid q = Session.query(DeliveryServer)\
class AccountsController(BaseController): "Accounts controller" def __before__(self): "set context" BaseController.__before__(self) if self.identity: c.user = self.identity['user'] else: c.user = None c.selectedtab = 'accounts' def _get_address(self, addressid): "return address" try: address = Session.query(Address).get(addressid) except NoResultFound: address = None return address def login(self): "login" if request.remote_addr in session: if session[request.remote_addr] > now(): abort(409, _('You have been banned after' ' several failed logins')) else: del session[request.remote_addr] session.save() identity = request.environ.get('repoze.who.identity') came_from = unquote(str(request.GET.get('came_from', ''))) if not came_from or ' ' in came_from: came_from = url('home') if '://' in came_from: from_url = urlparse(came_from) came_from = from_url[2] if identity: redirect(url(came_from)) else: c.came_from = came_from c.login_counter = request.environ['repoze.who.logins'] if c.login_counter >= 3: ban_until = now() + timedelta(minutes=5) if request.remote_addr not in session: session[request.remote_addr] = ban_until session.save() else: if now() > session[request.remote_addr]: del session[request.remote_addr] session.save() return render('/accounts/login.html') def loggedin(self): "Landing page" came_from = (unquote(str(request.params.get('came_from', ''))) or url('/')) if not self.identity: login_counter = request.environ['repoze.who.logins'] + 1 redirect( url('/accounts/login', came_from=came_from, __logins=login_counter)) userid = self.identity['repoze.who.userid'] user = self.identity['user'] if user is None: try: user = User(username=userid, email=userid) user.active = True local_part, domain = userid.split('@') domains = Session.query(Domain)\ .filter(Domain.name == domain)\ .all() user.domains = domains user.timezone = domains[0].timezone Session.add(user) Session.commit() msg = _('First time Login from external auth,' ' your local account was created') addresses = [] if ('tokens' in self.identity and 'ldap' in self.identity['tokens']): lsettings = Session.query(AuthServer.address, AuthServer.port, LDAPSettings.binddn, LDAPSettings.bindpw, LDAPSettings.usetls)\ .join(Domain)\ .filter(AuthServer.enabled == True)\ .filter(Domain.name == domain)\ .all() lsettings = lsettings[0] lurl = make_ldap_uri(lsettings.address, lsettings.port) base_dn = get_user_dn(self.identity['tokens'][1]) attributes = [ 'sn', 'givenName', 'proxyAddresses', 'mail', 'memberOf' ] ldapattributes = LDAPAttributes(lurl, base_dn, attributes=attributes, bind_dn=lsettings.binddn, bind_pass=lsettings.bindpw, start_tls=lsettings.usetls) ldapattributes() attrmap = { 'sn': 'lastname', 'givenName': 'firstname', 'mail': 'email', } update_attrs = False doms = [domains[0].name] doms.extend([alias.name for alias in domains[0].aliases]) for attr in attrmap: if (attr == 'mail' and attr in ldapattributes and ldapattributes[attr][0] == user.email): # Dont update if user.email = directory.email continue if (attr == 'mail' and attr in ldapattributes and '@' in ldapattributes[attr][0]): # Update if email is hosted by us if ldapattributes[attr][0].split('@')[1] in doms: setattr(user, attrmap[attr], ldapattributes[attr][0]) update_attrs = True continue if attr in ldapattributes: setattr(user, attrmap[attr], ldapattributes[attr][0]) update_attrs = True if update_attrs: Session.add(user) Session.commit() # accounts aliases if 'proxyAddresses' in ldapattributes: for mailaddr in ldapattributes['proxyAddresses']: try: if mailaddr.startswith('SMTP:'): continue if (mailaddr.startswith('smtp:') and mailaddr.strip('smtp:').lsplit('@')[1] in doms): # Only add domain if we host it address = Address( PROXY_ADDR_RE.sub('', mailaddr)) address.user = user addresses.append(address) except IndexError: pass # accounts groups if 'memberOf' in ldapattributes: for group_dn in ldapattributes['memberOf']: groupattributes = LDAPAttributes( lurl, group_dn, attributes=['proxyAddresses'], bind_dn=lsettings.binddn, bind_pass=lsettings.bindpw, start_tls=lsettings.usetls) groupattributes() for mailaddr in groupattributes['proxyAddresses']: try: mailaddr = mailaddr.lower() if (mailaddr.startswith('smtp:') and mailaddr.lstrip('smtp:').split( '@')[1] in doms): address = Address( PROXY_ADDR_RE.sub('', mailaddr)) address.user = user addresses.append(address) except IndexError: pass else: for alias in domains[0].aliases: address = Address('%s@%s' % (local_part, alias.name)) address.user = user addresses.append(address) for unsaved in addresses: try: Session.add(unsaved) Session.commit() except IntegrityError: Session.rollback() except IntegrityError: Session.rollback() redirect(url('/logout')) else: msg = _('Login successful, Welcome back %(username)s !' % dict(username=userid)) user.last_login = now() Session.add(user) Session.commit() if user.is_peleb: for domain in user.domains: if check_language(domain.language): session['lang'] = domain.language session.save() break session['taskids'] = [] session.save() info = ACCOUNTLOGIN_MSG % dict(u=user.username) audit_log(user.username, 6, info, request.host, request.remote_addr, now()) flash(msg) redirect(url(came_from)) def loggedout(self): "Logged out page" # response.delete_cookie('baruwacsrf') if 'lang' in session: lang = session['lang'] session.clear() if 'lang' in locals(): session['lang'] = lang session.save() #flash(_('You have been logged out !')) came_from = (unquote(str(request.params.get('came_from', ''))) or url('/accounts/login/')) redirect(url(came_from)) def passwdreset(self): """Render password reset page""" c.came_from = '/' c.login_counter = 0 return render('/accounts/login.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def pwchange(self, userid): """Reset a user password""" user = self._get_user(userid) if not user: abort(404) c.form = ChangePasswordForm(request.POST, csrf_context=session) if request.POST and c.form.validate(): if user.local: user.set_password(c.form.password1.data) Session.add(user) Session.commit() flash( _('The account password for %(name)s has been reset') % dict(name=user.username)) info = PASSWORDCHANGE_MSG % dict(u=user.username) audit_log(c.user.username, 2, info, request.host, request.remote_addr, now()) else: flash( _('This is an external account, use' ' external system to reset the password')) redirect(url('account-detail', userid=user.id)) c.id = userid c.username = user.username c.posturl = 'accounts-pw-change' return render('/accounts/pwchange.html') @ActionProtector(not_anonymous()) def upwchange(self, userid): """User change own password""" user = self._get_user(userid) if not user: abort(404) if user.id != c.user.id or c.user.is_superadmin: abort(403) c.form = UserPasswordForm(request.POST, csrf_context=session) if (request.POST and c.form.validate() and user.validate_password(c.form.password3.data)): if user.local: user.set_password(c.form.password1.data) Session.add(user) Session.commit() flash( _('The account password for %(name)s has been reset') % dict(name=user.username)) info = PASSWORDCHANGE_MSG % dict(u=user.username) audit_log(c.user.username, 2, info, request.host, request.remote_addr, now()) else: flash( _('This is an external account, use' ' external system to reset the password')) redirect(url('account-detail', userid=user.id)) elif (request.POST and not user.validate_password(c.form.password3.data) and not c.form.password3.errors): flash_alert( _('The old password supplied does' ' not match our records')) c.id = userid c.username = user.username c.posturl = 'accounts-pw-uchange' return render('/accounts/pwchange.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def index(self, page=1, orgid=None, domid=None, format=None): """GET /accounts/: Paginate items in the collection""" num_items = session.get('accounts_num_items', 10) c.form = BulkDelUsers(request.POST, csrf_context=session) if request.POST: if str(c.user.id) in c.form.accountid.data: c.form.accountid.data.remove(str(c.user.id)) if c.form.accountid.data and c.form.whatdo.data == 'disable': Session.query(User)\ .filter(User.id.in_(c.form.accountid.data))\ .update({'active':False}, synchronize_session='fetch') Session.commit() if c.form.accountid.data and c.form.whatdo.data == 'enable': Session.query(User)\ .filter(User.id.in_(c.form.accountid.data))\ .update({'active':True}, synchronize_session='fetch') Session.commit() if c.form.accountid.data and c.form.whatdo.data == 'delete': session['bulk_account_delete'] = c.form.accountid.data session.save() # redirect for confirmation redirect(url('accounts-confirm-delete')) users = Session.query(User.id, User.username, User.firstname, User.lastname, User.email, User.active, User.local, User.account_type).order_by(User.id) usrcount = Session.query(User.id) if c.user.is_domain_admin: users = users.join(domain_users, (dom_owns, domain_users.c.domain_id == dom_owns.c.domain_id), (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) usrcount = usrcount.join(domain_users, (dom_owns, domain_users.c.domain_id == dom_owns.c.domain_id), (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) if domid: users = users.filter( and_(domain_users.c.domain_id == domid, domain_users.c.user_id == User.id)) usrcount = usrcount.filter( and_(domain_users.c.domain_id == domid, domain_users.c.user_id == User.id)) if orgid: users = users.filter( and_( domain_users.c.user_id == User.id, domain_users.c.domain_id == dom_owns.c.domain_id, dom_owns.c.organization_id == orgid, )) usrcount = usrcount.filter( and_( domain_users.c.user_id == User.id, domain_users.c.domain_id == dom_owns.c.domain_id, dom_owns.c.organization_id == orgid, )) pages = paginate.Page(users, page=int(page), items_per_page=num_items, item_count=usrcount.count()) if format == 'json': response.headers['Content-Type'] = 'application/json' data = convert_acct_to_json(pages, orgid) return data c.page = pages c.domid = domid c.orgid = orgid return render('/accounts/index.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def search(self, format=None): "Search for accounts" total_found = 0 search_time = 0 num_items = session.get('accounts_num_items', 10) q = request.GET.get('q', '') d = request.GET.get('d', None) kwds = {'presliced_list': True} page = int(request.GET.get('p', 1)) conn = SphinxClient() conn.SetMatchMode(SPH_MATCH_EXTENDED2) conn.SetFieldWeights( dict(username=50, email=30, firstname=10, lastname=10)) if page == 1: conn.SetLimits(0, num_items, 500) else: page = int(page) offset = (page - 1) * num_items conn.SetLimits(offset, num_items, 500) if d: conn.SetFilter('domains', [ int(d), ]) if c.user.is_domain_admin: #crcs = get_dom_crcs(Session, c.user) domains = Session.query(Domain.id).join(dom_owns, (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) conn.SetFilter('domains', [domain[0] for domain in domains]) q = clean_sphinx_q(q) results = conn.Query(q, 'accounts, accounts_rt') q = restore_sphinx_q(q) if results and results['matches']: ids = [hit['id'] for hit in results['matches']] total_found = results['total_found'] search_time = results['time'] users = Session.query(User.id, User.username, User.firstname, User.lastname, User.email, User.active, User.local, User.account_type)\ .filter(User.id.in_(ids))\ .order_by(User.id)\ .all() usercount = total_found else: users = [] usercount = 0 c.q = q c.d = d c.total_found = total_found c.search_time = search_time c.page = paginate.Page(users, page=int(page), items_per_page=num_items, item_count=usercount, **kwds) return render('/accounts/searchresults.html') @ActionProtector(All(not_anonymous(), CanAccessAccount())) def detail(self, userid): """GET /accounts/userid/ Show a specific item""" user = self._get_user(userid) if not user: abort(404) c.account = user return render('/accounts/account.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def add(self): """/accounts/new""" c.form = AddUserForm(request.POST, csrf_context=session) if c.user.is_domain_admin: account_types = (('3', 'User'), ) c.form.account_type.choices = account_types c.form.domains.query = Session.query(Domain).join(dom_owns, (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) else: c.form.domains.query = Session.query(Domain) if request.POST and c.form.validate(): try: user = User(username=c.form.username.data, email=c.form.email.data) for attr in [ 'firstname', 'lastname', 'email', 'active', 'account_type', 'send_report', 'spam_checks', 'low_score', 'high_score', 'timezone' ]: setattr(user, attr, getattr(c.form, attr).data) user.local = True user.set_password(c.form.password1.data) if int(user.account_type) == 3: user.domains = c.form.domains.data Session.add(user) Session.commit() update_serial.delay() info = ADDACCOUNT_MSG % dict(u=user.username) audit_log(c.user.username, 3, info, request.host, request.remote_addr, now()) flash( _('The account: %(user)s was created successfully') % {'user': c.form.username.data}) redirect(url('account-detail', userid=user.id)) except IntegrityError: Session.rollback() flash_alert( _('Either the username or email address already exist')) return render('/accounts/new.html') @ActionProtector(All(not_anonymous(), CanAccessAccount())) def edit(self, userid): """GET /accounts/edit/id: Form to edit an existing item""" user = self._get_user(userid) if not user: abort(404) c.form = EditUserForm(request.POST, user, csrf_context=session) c.form.domains.query = Session.query(Domain) if user.account_type != 3 or c.user.is_peleb: del c.form.domains if c.user.is_peleb: del c.form.username del c.form.email del c.form.active if request.POST and c.form.validate(): update = False kwd = dict(userid=userid) for attr in FORM_FIELDS: field = getattr(c.form, attr) if field and field.data != getattr(user, attr): setattr(user, attr, field.data) update = True if update: try: Session.add(user) Session.commit() update_serial.delay() flash(_('The account has been updated')) kwd['uc'] = 1 info = UPDATEACCOUNT_MSG % dict(u=user.username) audit_log(c.user.username, 2, info, request.host, request.remote_addr, now()) except IntegrityError: Session.rollback() flash_alert( _('The account: %(acc)s could not be updated') % dict(acc=user.username)) if (user.id == c.user.id and c.form.active and c.form.active.data == False): redirect(url('/logout')) else: flash_info(_('No changes made to the account')) redirect(url(controller='accounts', action='detail', **kwd)) c.fields = FORM_FIELDS c.id = userid return render('/accounts/edit.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def delete(self, userid): """/accounts/delete/id""" user = self._get_user(userid) if not user: abort(404) c.form = EditUserForm(request.POST, user, csrf_context=session) c.form.domains.query = Session.query(Domain) if request.POST and c.form.validate(): username = user.username Session.delete(user) Session.commit() update_serial.delay() flash(_('The account has been deleted')) info = DELETEACCOUNT_MSG % dict(u=username) audit_log(c.user.username, 4, info, request.host, request.remote_addr, now()) if userid == c.user.id: redirect(url('/logout')) redirect(url(controller='accounts', action='index')) else: flash_info( _('The account: %(a)s and all associated data' ' will be deleted, This action is not reversible.') % dict(a=user.username)) c.fields = FORM_FIELDS c.id = userid return render('/accounts/delete.html') @ActionProtector(not_anonymous()) def confirm_delete(self): "Confirm mass delete" accountids = session.get('bulk_account_delete', []) if not accountids: redirect(url(controller='accounts', action='index')) num_items = 10 if len(accountids) > num_items and len(accountids) <= 20: num_items = 20 if len(accountids) > num_items and len(accountids) <= 50: num_items = 50 if len(accountids) > num_items and len(accountids) <= 100: num_items = 100 users = Session.query(User).filter(User.id.in_(accountids)) usrcount = Session.query(User.id) if c.user.is_domain_admin and usrcount: users = users.join(domain_users, (dom_owns, domain_users.c.domain_id == dom_owns.c.domain_id), (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) usrcount = usrcount.join(domain_users, (dom_owns, domain_users.c.domain_id == dom_owns.c.domain_id), (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id) if request.POST: tasks = [] # try: for account in users.all(): info = DELETEACCOUNT_MSG % dict(u=account.username) Session.delete(account) tasks.append([ c.user.username, 4, info, request.host, request.remote_addr, now() ]) Session.commit() # except DataError: # flash_alert(_('An error occured try again')) # redirect(url(controller='accounts', action='index')) del session['bulk_account_delete'] session.save() update_serial.delay() for task in tasks: audit_log(*task) flash(_('The accounts have been deleted')) redirect(url(controller='accounts')) else: flash( _('The following accounts are about to be deleted,' ' this action is not reversible, Do you wish to ' 'continue ?')) try: c.page = paginate.Page(users, page=1, items_per_page=num_items, item_count=usrcount.count()) except DataError: flash_alert(_('An error occured try again')) redirect(url(controller='accounts', action='index')) return render('/accounts/confirmbulkdel.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def addaddress(self, userid): "Add address" user = self._get_user(userid) if not user: abort(404) c.form = AddressForm(request.POST, csrf_context=session) if request.POST and c.form.validate(): try: if c.user.is_domain_admin: # check if they own the domain domain = c.form.address.data.split('@')[1] domain = Session.query(Domain).options( joinedload('organizations')).join( dom_owns, (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id)\ .filter(Domain.name == domain).one() addr = Address(address=c.form.address.data) addr.enabled = c.form.enabled.data addr.user = user Session.add(addr) Session.commit() update_serial.delay() info = ADDRADD_MSG % dict(a=addr.address, ac=user.username) audit_log(c.user.username, 3, info, request.host, request.remote_addr, now()) flash( _('The alias address %(address)s was successfully created.' % dict(address=addr.address))) except IntegrityError: flash_alert( _('The address %(addr)s already exists') % dict(addr=addr.address)) except NoResultFound: flash( _('Domain: %(d)s does not belong to you') % dict(d=domain)) redirect(url(controller='accounts', action='detail', userid=userid)) c.id = userid return render('/accounts/addaddress.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def editaddress(self, addressid): "Edit address" address = self._get_address(addressid) if not address: abort(404) c.form = AddressForm(request.POST, address, csrf_context=session) if request.POST and c.form.validate(): try: if (address.address != c.form.address.data or address.enabled != c.form.enabled.data): if c.user.is_domain_admin: # check if they own the domain domain = c.form.address.data.split('@')[1] domain = Session.query(Domain).options( joinedload('organizations')).join( dom_owns, (oas, dom_owns.c.organization_id == oas.c.organization_id))\ .filter(oas.c.user_id == c.user.id)\ .filter(Domain.name == domain).one() address.address = c.form.address.data address.enabled = c.form.enabled.data Session.add(address) Session.commit() update_serial.delay() info = ADDRUPDATE_MSG % dict(a=address.address, ac=address.user.username) audit_log(c.user.username, 2, info, request.host, request.remote_addr, now()) flash(_('The alias address has been updated')) else: flash_info(_('No changes were made to the address')) except IntegrityError: flash_alert( _('The address %(addr)s already exists') % dict(addr=c.form.address.data)) except NoResultFound: flash( _('Domain: %(d)s does not belong to you') % dict(d=domain)) redirect( url(controller='accounts', action='detail', userid=address.user_id)) c.id = addressid c.userid = address.user_id return render('/accounts/editaddress.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers(), CanAccessAccount())) def deleteaddress(self, addressid): "Delete address" address = self._get_address(addressid) if not address: abort(404) c.form = AddressForm(request.POST, address, csrf_context=session) if request.POST and c.form.validate(): user_id = address.user_id addr = address.address username = address.user.username Session.delete(address) Session.commit() update_serial.delay() info = ADDRDELETE_MSG % dict(a=addr, ac=username) audit_log(c.user.username, 4, info, request.host, request.remote_addr, now()) flash(_('The address has been deleted')) redirect( url(controller='accounts', action='detail', userid=user_id)) c.id = addressid c.userid = address.user_id return render('/accounts/deleteaddress.html') def set_language(self): "Set the language" nextpage = request.params.get('next', None) if not nextpage: nextpage = request.headers.get('Referer', None) if not nextpage: nextpage = '/' if '://' in nextpage: from_url = urlparse(nextpage) nextpage = from_url[2] lang_code = request.params.get('language', None) if lang_code and check_language(lang_code): session['lang'] = lang_code session.save() params = [] for param in request.params: if not param in ['language', 'amp']: value = request.params[param] if value: if (param == 'came_from' and '://' in urllib2.unquote(value)): urlparts = urlparse(urllib2.unquote(value)) value = urlparts[2] or '/' params.append('%s=%s' % (urllib2.quote(param), urllib2.quote(value))) if params: nextpage = "%s?%s" % (nextpage, '&'.join(params)) redirect(nextpage) @ActionProtector(All(not_anonymous(), OwnsDomain())) def import_accounts(self, domainid): "import accounts" try: cachekey = u'domain-%s' % domainid domain = Session.query(Domain.id, Domain.name)\ .filter(Domain.id==domainid)\ .options(FromCache('sql_cache_med', cachekey)).one() except NoResultFound: abort(404) c.form = ImportCSVForm(request.POST, csrf_context=session) if request.POST and c.form.validate(): basedir = config['pylons.cache_dir'] csvdata = request.POST['csvfile'] if hasattr(csvdata, 'filename'): dstfile = os.path.join(basedir, 'uploads', csvdata.filename.lstrip(os.sep)) if not os.path.exists(dstfile) and iscsv(csvdata.file): csvfile = open(dstfile, 'w') shutil.copyfileobj(csvdata.file, csvfile) csvdata.file.close() csvfile.close() task = importaccounts.apply_async(args=[ domainid, dstfile, c.form.skipfirst.data, c.user.id ]) session['taskids'].append(task.task_id) session['acimport-count'] = 1 session['acimport-file'] = dstfile session.save() flash( _('File uploaded, and is being processed, this page' ' will automatically refresh to show the status')) redirect(url('accounts-import-status', taskid=task.task_id)) else: filename = csvdata.filename.lstrip(os.sep) if not iscsv(csvdata.file): flash_alert( _('The file: %s is not a CSV file') % filename) else: flash_alert( _('The file: %s already exists and' ' is being processed.') % filename) csvdata.file.close() else: flash_alert(_('No CSV was file uploaded, try again')) c.domain = domain return render('/accounts/importaccounts.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def import_status(self, taskid): "import status" result = AsyncResult(taskid) if result is None or taskid not in session['taskids']: flash(_('The task status requested has expired or does not exist')) redirect(url(controller='accounts', action='index')) if result.ready(): finished = True flash.pop_messages() if isinstance(result.result, Exception): if c.user.is_superadmin: flash_alert( _('Error occured in processing %s') % result.result) else: flash_alert(_('Backend error occured during processing.')) redirect(url(controller='accounts')) update_serial.delay() audit_log(c.user.username, 3, ACCOUNTIMPORT_MSG, request.host, request.remote_addr, now()) else: session['acimport-count'] += 1 if (session['acimport-count'] >= 10 and result.state in ['PENDING', 'RETRY', 'FAILURE']): result.revoke() try: os.unlink(session['acimport-file']) except OSError: pass del session['acimport-count'] session.save() flash_alert( _('The import could not be processed,' ' try again later')) redirect(url(controller='accounts')) finished = False c.finished = finished c.results = result.result c.success = result.successful() return render('/accounts/importstatus.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def export_accounts(self, domainid=None, orgid=None): "export domains" task = exportaccounts.apply_async(args=[domainid, c.user.id, orgid]) if not 'taskids' in session: session['taskids'] = [] session['taskids'].append(task.task_id) session['acexport-count'] = 1 session.save() flash(_('Accounts export is being processed')) redirect(url('accounts-export-status', taskid=task.task_id)) @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def export_status(self, taskid): "export status" result = AsyncResult(taskid) if result is None or taskid not in session['taskids']: flash(_('The task status requested has expired or does not exist')) redirect(url(controller='accounts', action='index')) if result.ready(): finished = True flash.pop_messages() if isinstance(result.result, Exception): if c.user.is_superadmin: flash_alert( _('Error occured in processing %s') % result.result) else: flash_alert(_('Backend error occured during processing.')) redirect(url(controller='accounts', action='index')) results = dict( f=True if not result.result['global_error'] else False, id=taskid, global_error=result.result['global_error']) audit_log(c.user.username, 5, ACCOUNTEXPORT_MSG, request.host, request.remote_addr, now()) else: session['acexport-count'] += 1 if (session['acexport-count'] >= 10 and result.state in ['PENDING', 'RETRY', 'FAILURE']): result.revoke() del session['acexport-count'] session.save() flash_alert( _('The export could not be processed, try again later')) redirect(url(controller='accounts', action='index')) finished = False results = dict(f=None, global_error=None) c.finished = finished c.results = results c.success = result.successful() d = request.GET.get('d', None) if finished and (d and d == 'y'): response.content_type = 'text/csv' response.headers['Cache-Control'] = 'max-age=0' csvdata = result.result['f'] disposition = 'attachment; filename=accounts-export-%s.csv' % taskid response.headers['Content-Disposition'] = str(disposition) response.headers['Content-Length'] = len(csvdata) return csvdata return render('/accounts/exportstatus.html') @ActionProtector(All(not_anonymous(), OnlyAdminUsers())) def setnum(self, format=None): "Set number of account items returned" num = check_num_param(request) if num and num in [10, 20, 50, 100]: session['accounts_num_items'] = num session.save() nextpage = request.headers.get('Referer', '/') if '://' in nextpage: from_url = urlparse(nextpage) nextpage = from_url[2] redirect(nextpage)
class StatusController(BaseController): def __before__(self): "set context" BaseController.__before__(self) if self.identity: c.user = self.identity['user'] else: c.user = None c.selectedtab = 'status' def _get_server(self, serverid): "utility" try: server = Session.query(Server).get(serverid) except NoResultFound: server = None return server @ActionProtector(OnlyAdminUsers()) def index(self): "System status" c.servers = Session.query(Server).filter( Server.hostname != 'default').all() labels = dict(clean=_('Clean'), highspam=_('High scoring spam'), lowspam=_('Low scoring spam'), virii=_('Virus infected'), infected=_('Policy blocked')) pie_colors = dict(clean='#006400', highspam='#FF0000', lowspam='#ffa07a', virii='#000000', infected='#d2691e') jsondata = [dict(tooltip=labels[attr], y=getattr(c.baruwa_totals, attr), stroke='black', color=pie_colors[attr]) for attr in ['clean', 'highspam', 'lowspam', 'virii', 'infected'] if getattr(c.baruwa_totals, attr)] c.chart_data = json.dumps(jsondata) return render('/status/index.html') def graph(self, nodeid=None): "Generate graphical system status" totals = DailyTotals(Session, c.user) if nodeid: server = self._get_server(nodeid) if not server: abort(404) baruwa_totals = totals.get(hostname=server.hostname) else: baruwa_totals = totals.get() if not baruwa_totals.total: abort(404) piedata = [] labels = [] for attr in ['clean', 'highspam', 'lowspam', 'virii', 'infected']: value = getattr(baruwa_totals, attr) or 0 piedata.append(value) if baruwa_totals.total > 0: labels.append(("%.1f%% %s" % ((1.0 * value / baruwa_totals.total) * 100, attr))) else: labels.append('0%') pie = PieChart(350, 264) pie.chart.labels = labels pie.chart.data = piedata pie.chart.width = 180 pie.chart.height = 180 pie.chart.x = 90 pie.chart.y = 30 pie.chart.slices.strokeWidth = 1 pie.chart.slices.strokeColor = colors.black pie.chart.slices[0].fillColor = PIE_CHART_COLORS[5] pie.chart.slices[1].fillColor = PIE_CHART_COLORS[0] pie.chart.slices[2].fillColor = PIE_CHART_COLORS[1] pie.chart.slices[3].fillColor = PIE_CHART_COLORS[9] pie.chart.slices[4].fillColor = PIE_CHART_COLORS[3] self.imgfile = StringIO() renderPM.drawToFile(pie, self.imgfile, 'PNG', bg=colors.HexColor('#ffffff')) response.content_type = 'image/png' response.headers['Cache-Control'] = 'max-age=0' return self.imgfile.getvalue() def mailq(self, serverid=None, queue='inbound', page=1, direction='dsc', order_by='timestamp'): "Display mailqueue" server = None if not serverid is None: server = self._get_server(serverid) if not server: abort(404) c.queue = queue c.server = server c.direction = direction c.order_by = desc(order_by) if direction == 'dsc' else order_by if queue == 'inbound': qdirection = 1 else: qdirection = 2 num_items = session.get('mailq_num_items', 10) query = Session.query(MailQueueItem).filter( MailQueueItem.direction == qdirection).order_by(order_by) if server: query = query.filter(MailQueueItem.hostname == server.hostname) uquery = UserFilter(Session, c.user, query, model=MailQueueItem) query = uquery.filter() c.form = MailQueueProcessForm(request.POST, csrf_context=session) pages = paginate.Page(query, page=int(page), items_per_page=num_items) choices = [(str(item.id), item.id) for item in pages.items] session['queue_choices'] = choices session.save() c.page = paginate.Page(query, page=int(page), items_per_page=num_items) return render('/status/mailq.html') def process_mailq(self): "Process mailq" sendto = url('mailq-status') choices = session.get('queue_choices', []) form = MailQueueProcessForm(request.POST, csrf_context=session) form.id.choices = choices if request.POST and form.validate() and choices: queueids = form.id.data if form.queue_action.data != '0': hosts = {} direction = None queueitems = Session.query(MailQueueItem)\ .filter(MailQueueItem.id.in_(queueids))\ .all() for item in queueitems: if not item.hostname in hosts: hosts[item.hostname] = [] if not direction: direction = item.direction hosts[item.hostname].append(item.messageid) for hostname in hosts: process_queued_msgs.apply_async(args=[hosts[hostname], form.queue_action.data, direction], queue=hostname) flash(_('The request has been queued for processing')) session['queue_choices'] = [] session.save() referer = request.headers.get('Referer', None) if referer and '/mailq' in referer: sendto = referer redirect(sendto) def mailq_detail(self, queueid): "View a queued message's details" query = Session.query(MailQueueItem) uquery = UserFilter(Session, c.user, query, model=MailQueueItem) query = uquery.filter() try: mailqitem = query.filter(MailQueueItem.id == queueid).one() except NoResultFound: #abort(404) flash_alert(_('The requested queued message was not found.')) redirect(url('mailq-status')) c.mailqitem = mailqitem c.form = MailQueueProcessForm(request.POST, csrf_context=session) session['queue_choices'] = [(queueid, queueid),] session.save() return render('/status/detail.html') def mailq_preview(self, queueid, attachid=None, imgid=None, allowimgs=None): "preview a queued message" query = Session.query(MailQueueItem) uquery = UserFilter(Session, c.user, query, model=MailQueueItem) query = uquery.filter() try: mailqitem = query.filter(MailQueueItem.id == queueid).one() except NoResultFound: flash_alert(_('The requested queued message was not found.')) redirect(url('mailq-status')) try: task = preview_queued_msg.apply_async(args=[mailqitem.messageid, mailqitem.direction, attachid, imgid], queue=mailqitem.hostname) task.wait(30) if task.result: if imgid: response.content_type = task.result['content_type'] if task.result and 'img' in task.result: info = QUEUEDOWNLOAD_MSG % dict(m=mailqitem.messageid, a=task.result['name']) audit_log(c.user.username, 1, info, request.host, request.remote_addr, datetime.now()) return base64.decodestring(task.result['img']) abort(404) if attachid: info = QUEUEDOWNLOAD_MSG % dict(m=mailqitem.messageid, a=task.result['name']) audit_log(c.user.username, 1, info, request.host, request.remote_addr, datetime.now()) response.content_type = task.result['mimetype'] dispos = 'attachment; filename="%s"' % task.result['name'] response.headers['Content-Disposition'] = dispos content_len = len(task.result['attachment']) response.headers['Content-Length'] = content_len response.headers['Pragma'] = 'public' response.headers['Cache-Control'] = 'max-age=0' return base64.decodestring(task.result['attachment']) for part in task.result['parts']: if part['type'] == 'html': html = fromstring(part['content']) for element, attribute, link, pos in iterlinks(html): if not link.startswith('cid:'): if not allowimgs and attribute == 'src': element.attrib['src'] = '%simgs/blocked.gif' % media_url() element.attrib['title'] = link flash(_('This message contains external images, which have been blocked. ') + literal(link_to(_('Display images'), url('queue-preview-with-imgs', queueid=queueid), class_='uline'))) else: imgname = link.replace('cid:', '') element.attrib['src'] = url('queue-preview-img', imgid=imgname.replace('/', '__xoxo__'), queueid=queueid) part['content'] = tostring(html) c.message = task.result info = QUEUEPREVIEW_MSG % dict(m=mailqitem.messageid) audit_log(c.user.username, 1, info, request.host, request.remote_addr, datetime.now()) else: raise TimeoutError except (TimeoutError, QueueNotFound): flash_alert(_('The message could not be processed')) redirect(url('mailq-status')) c.queueid = queueid c.messageid = mailqitem.messageid return render('/status/preview.html') @ActionProtector(OnlySuperUsers()) def server_status(self, serverid): "Display server status" server = self._get_server(serverid) if not server: abort(404) totals = DailyTotals(Session, c.user) mailq = MailQueue(Session, c.user) totals = totals.get(server.hostname) inbound = mailq.get(1, server.hostname)[0] outbound = mailq.get(2, server.hostname)[0] statusdict = dict(total=totals.total, mta=0, scanners=0, av=0, clean_mail=totals.clean, high_spam=totals.highspam, virii=totals.virii, spam_mail=totals.lowspam, inq=inbound, outq=outbound, otherinfected=totals.infected, uptime='Unknown', load=(0, 0, 0), mem=dict(free=0, used=0, total=0, percent=0), partitions=[], net={}, cpu=0) try: task = systemstatus.apply_async(queue=server.hostname) task.wait(30) hoststatus = task.result statusdict.update(hoststatus) info = HOSTSTATUS_MSG % dict(n=server.hostname) audit_log(c.user.username, 1, info, request.host, request.remote_addr, datetime.now()) except (TimeoutError, QueueNotFound): pass c.server = server c.status = statusdict return render('/status/serverstatus.html') @ActionProtector(OnlySuperUsers()) def server_bayes_status(self, serverid): "Display bayes stats" server = self._get_server(serverid) if not server: abort(404) try: task = bayesinfo.apply_async(queue=server.hostname) task.wait(30) result = task.result info = HOSTBAYES_MSG % dict(n=server.hostname) audit_log(c.user.username, 1, info, request.host, request.remote_addr, datetime.now()) except (TimeoutError, QueueNotFound): result = {} c.server = server c.data = result return render('/status/bayes.html') @ActionProtector(OnlySuperUsers()) def server_salint_stat(self, serverid): "Display server salint output" server = self._get_server(serverid) if not server: abort(404) try: task = salint.apply_async(queue=server.hostname) task.wait(30) result = task.result info = HOSTSALINT_MSG % dict(n=server.hostname) audit_log(c.user.username, 1, info, request.host, request.remote_addr, datetime.now()) except (TimeoutError, QueueNotFound): result = [] c.server = server c.data = result return render('/status/salint.html') @ActionProtector(OnlySuperUsers()) def audit(self, page=1, format=None): "Audit log" total_found = 0 search_time = 0 num_items = session.get('auditlog_num_items', 50) q = request.GET.get('q', None) kwds = {} if q: conn = SphinxClient() conn.SetMatchMode(SPH_MATCH_EXTENDED2) if page == 1: conn.SetLimits(0, num_items, 500) else: page = int(page) offset = (page - 1) * num_items conn.SetLimits(offset, num_items, 500) q = clean_sphinx_q(q) results = conn.Query(q, 'auditlog, auditlog_rt') q = restore_sphinx_q(q) if results and results['matches']: ids = [hit['id'] for hit in results['matches']] query = Session.query(AuditLog)\ .filter(AuditLog.id.in_(ids))\ .order_by(desc('timestamp'))\ .all() total_found = results['total_found'] search_time = results['time'] logcount = total_found kwds['presliced_list'] = True else: query = [] lcount = 0 logcount = 0 else: query = Session.query(AuditLog)\ .order_by(desc('timestamp')) lcount = Session.query(AuditLog)\ .order_by(desc('timestamp')) if not 'logcount' in locals(): logcount = lcount.count() items = paginate.Page(query, page=int(page), items_per_page=num_items, item_count=logcount, **kwds) if format == 'json': response.headers['Content-Type'] = 'application/json' jdict = convert_settings_to_json(items) if q: encoded = json.loads(jdict) encoded['q'] = q jdict = json.dumps(encoded) return jdict c.page = items c.q = q c.total_found = total_found c.search_time = search_time return render('/status/audit.html') @ActionProtector(OnlySuperUsers()) def audit_export(self, isquery=None, format=None): "Export audit logs" query = request.GET.get('q', None) if isquery and query is None: flash_alert(_('No query specified for audit log export')) redirect(url('status-audit-logs')) task = export_auditlog.apply_async(args=[format, query]) if not 'taskids' in session: session['taskids'] = [] session['taskids'].append(task.task_id) session['exportauditlog-counter'] = 1 session.save() redirect(url('status-auditlog-export-status', taskid=task.task_id)) @ActionProtector(OnlySuperUsers()) def audit_export_status(self, taskid): "Audit log export status" result = AsyncResult(taskid) if result is None or taskid not in session['taskids']: flash(_('The task status requested has expired or does not exist')) redirect(url('status-audit-logs')) if result.ready(): finished = True flash.pop_messages() if isinstance(result.result, Exception): if c.user.is_superadmin: flash_alert(_('Error occured in processing %s') % result.result) else: flash_alert(_('Backend error occured during processing.')) redirect(url('status-audit-logs')) else: session['exportauditlog-counter'] += 1 session.save() if (session['exportauditlog-counter'] >= 20 and result.state in ['PENDING', 'RETRY', 'FAILURE']): result.revoke() del session['exportauditlog-counter'] session.save() flash_alert(_('The audit log export failed, try again later')) redirect(url('status-audit-logs')) finished = False c.finished = finished c.results = result.result c.success = result.successful() d = request.GET.get('d', None) if finished and (d and d == 'y'): audit_log(c.user.username, 5, AUDITLOGEXPORT_MSG, request.host, request.remote_addr, datetime.now()) response.content_type = result.result['content_type'] response.headers['Cache-Control'] = 'max-age=0' respdata = result.result['f'] disposition = 'attachment; filename=%s' % result.result['filename'] response.headers['Content-Disposition'] = disposition response.headers['Content-Length'] = len(respdata) return respdata return render('/status/auditexportstatus.html') def setnum(self, format=None): "Set number of items to return for auditlog/mailq" app = request.GET.get('ac', 'mailq') num = check_num_param(request) if num and num in [10, 20, 50, 100]: if app == 'auditlog': session['auditlog_num_items'] = num else: session['mailq_num_items'] = num session.save() nextpage = request.headers.get('Referer', '/') if '://' in nextpage: from_url = urlparse(nextpage) nextpage = from_url[2] redirect(nextpage)
class StatusController(BaseController): "status controller" def __before__(self): "set context" BaseController.__before__(self) if self.identity: c.user = self.identity['user'] else: c.user = None c.selectedtab = 'status' def _get_server(self, serverid): "utility" try: server = Session.query(Server).get(serverid) except NoResultFound: server = None return server @ActionProtector(OnlyAdminUsers()) def index(self): "System status" c.servers = Session.query(Server)\ .filter(Server.hostname != 'default')\ .filter(Server.enabled == true()).all() jsondata = [ dict(tooltip=LABELS[attr], y=getattr(c.baruwa_totals, attr), stroke='black', color=PIE_COLORS[attr]) for attr in ['clean', 'highspam', 'lowspam', 'virii', 'infected'] if getattr(c.baruwa_totals, attr) ] c.chart_data = json.dumps(jsondata) return self.render('/status/index.html') def graph(self, nodeid=None): "Generate graphical system status" totals = DailyTotals(Session, c.user) if nodeid: server = self._get_server(nodeid) if not server: abort(404) baruwa_totals = totals.get(hostname=server.hostname) else: baruwa_totals = totals.get() if not baruwa_totals.total: abort(404) piedata = [] labels = [] for attr in ['clean', 'highspam', 'lowspam', 'virii', 'infected']: value = getattr(baruwa_totals, attr) or 0 piedata.append(value) if baruwa_totals.total > 0: labels.append( ("%.1f%% %s" % ((1.0 * value / baruwa_totals.total) * 100, attr))) else: labels.append('0%') pie = PieChart(350, 264) pie.chart.labels = labels pie.chart.data = piedata pie.chart.width = 180 pie.chart.height = 180 pie.chart.x = 90 pie.chart.y = 30 pie.chart.slices.strokeWidth = 1 pie.chart.slices.strokeColor = colors.black pie.chart.slices[0].fillColor = PIE_CHART_COLORS[5] pie.chart.slices[1].fillColor = PIE_CHART_COLORS[0] pie.chart.slices[2].fillColor = PIE_CHART_COLORS[1] pie.chart.slices[3].fillColor = PIE_CHART_COLORS[9] pie.chart.slices[4].fillColor = PIE_CHART_COLORS[3] self.imgfile = StringIO() renderPM.drawToFile(pie, self.imgfile, 'PNG', bg=colors.HexColor('#ffffff')) response.content_type = 'image/png' response.headers['Cache-Control'] = 'max-age=0' return self.imgfile.getvalue() def mailq(self, serverid=None, queue='inbound', page=1, direction='dsc', order_by='timestamp'): "Display mailqueue" server = None if serverid is not None: server = self._get_server(serverid) if not server: abort(404) c.queue = queue c.server = server c.direction = direction c.order_by = desc(order_by) if direction == 'dsc' else order_by if queue == 'inbound': qdirection = 1 else: qdirection = 2 num_items = session.get('mailq_num_items', 10) query = Session.query(MailQueueItem).filter( MailQueueItem.direction == qdirection).order_by(order_by) if server: query = query.filter(MailQueueItem.hostname == server.hostname) uquery = UserFilter(Session, c.user, query, model=MailQueueItem) query = uquery.filter() c.form = MailQueueProcessForm(request.POST, csrf_context=session) pages = paginate.Page(query, page=int(page), items_per_page=num_items) choices = [(str(item.id), item.id) for item in pages.items] session['queue_choices'] = choices session.save() c.page = paginate.Page(query, page=int(page), items_per_page=num_items) return self.render('/status/mailq.html') def process_mailq(self): "Process mailq" sendto = url('mailq-status') choices = session.get('queue_choices', []) form = MailQueueProcessForm(request.POST, csrf_context=session) form.id.choices = choices if request.method == 'POST' and form.validate() and choices: queueids = form.id.data if form.queue_action.data != '0': hosts = {} direction = None queueitems = Session.query(MailQueueItem)\ .filter(MailQueueItem.id.in_(queueids))\ .all() for item in queueitems: if item.hostname not in hosts: hosts[item.hostname] = [] if not direction: direction = item.direction hosts[item.hostname].append(item.messageid) for hostname in hosts: process_queued_msgs.apply_async(args=[ hosts[hostname], form.queue_action.data, direction ], routing_key=hostname) flash(_('The request has been queued for processing')) session['queue_choices'] = [] session.save() referer = request.headers.get('Referer', None) if referer and '/mailq' in referer: sendto = referer redirect(sendto) def mailq_detail(self, queueid): "View a queued message's details" query = Session.query(MailQueueItem) uquery = UserFilter(Session, c.user, query, model=MailQueueItem) query = uquery.filter() try: mailqitem = query.filter(MailQueueItem.id == queueid).one() except NoResultFound: # abort(404) flash_alert(_('The requested queued message was not found.')) redirect(url('mailq-status')) c.mailqitem = mailqitem c.form = MailQueueProcessForm(request.POST, csrf_context=session) session['queue_choices'] = [ (queueid, queueid), ] session.save() return self.render('/status/detail.html') def mailq_preview(self, queueid, attachid=None, imgid=None, allowimgs=None, richformat=None): "preview a queued message" query = Session.query(MailQueueItem) uquery = UserFilter(Session, c.user, query, model=MailQueueItem) query = uquery.filter() try: mailqitem = query.filter(MailQueueItem.id == queueid).one() except NoResultFound: flash_alert(_('The requested queued message was not found.')) redirect(url('mailq-status')) try: task = preview_queued_msg.apply_async( args=[ mailqitem.messageid, mailqitem.direction, attachid, imgid ], routing_key=mailqitem.hostname) task.wait(30) if task.result: if imgid: response.content_type = task.result['content_type'] if task.result and 'img' in task.result: info = QUEUEDOWNLOAD_MSG % dict(m=mailqitem.messageid, a=task.result['name']) audit_log(c.user.username, 1, unicode(info), request.host, request.remote_addr, arrow.utcnow().datetime) return base64.decodestring(task.result['img']) abort(404) if attachid: info = QUEUEDOWNLOAD_MSG % dict(m=mailqitem.messageid, a=task.result['name']) audit_log(c.user.username, 1, unicode(info), request.host, request.remote_addr, arrow.utcnow().datetime) response.content_type = task.result['mimetype'] dispos = 'attachment; filename="%s"' % task.result['name'] response.headers['Content-Disposition'] = str(dispos) content_len = len(task.result['attachment']) response.headers['Content-Length'] = content_len response.headers['Pragma'] = 'public' response.headers['Cache-Control'] = 'max-age=0' return base64.decodestring(task.result['attachment']) for part in task.result['parts']: if part['type'] == 'text/html': local_rf = (not task.result['is_multipart'] or richformat) part['content'] = img_fixups(part['content'], queueid, allowimgs, local_rf) c.message = task.result info = QUEUEPREVIEW_MSG % dict(m=mailqitem.messageid) audit_log(c.user.username, 1, unicode(info), request.host, request.remote_addr, arrow.utcnow().datetime) else: raise TimeoutError except (TimeoutError, QueueNotFound), error: msg = _('The message could not be processed') flash_alert(msg) msg = _('The message could not be processed: %s') % error log.info(msg) redirect(url('mailq-status')) c.queueid = queueid c.messageid = mailqitem.messageid c.richformat = richformat return self.render('/status/preview.html')