<commit_before># -*- test-case-name: vumi.tests.test_utils -*-

import os.path
import re
import sys
import base64

import pkg_resources
from zope.interface import implements
from twisted.internet import defer
from twisted.internet import reactor, protocol
from twisted.internet.defer import succeed
from twisted.web.client import Agent, ResponseDone
from twisted.web.http_headers import Headers
from twisted.web.iweb import IBodyProducer
from twisted.web.http import PotentialDataLoss

def import_module(name):
    """
    This is a simpler version of `importlib.import_module` and does
    not support relative imports.

    It's here so that we can avoid using importlib and not have to
    juggle different deps between Python versions.
    """
    __import__(name)
    return sys.modules[name]

class SimplishReceiver(protocol.Protocol):
    def __init__(self, response):
        self.deferred = defer.Deferred()
        self.response = response
        self.response.delivered_body = ''
        if response.code == 204:
            self.deferred.callback(self.response)
        else:
            response.deliverBody(self)

    def dataReceived(self, data):
        self.response.delivered_body += data

    def connectionLost(self, reason):
        if reason.check(ResponseDone):
            self.deferred.callback(self.response)
        elif reason.check(PotentialDataLoss):
            # This is only (and always!) raised if we have an HTTP 1.0 request
            # with no Content-Length.
            # See http://twistedmatrix.com/trac/ticket/4840 for sadness.
            #
            # We ignore this and treat the call as success. If we care about
            # checking for potential data loss, we should do that in all cases
            # rather than trying to figure out if we might need to.
            self.deferred.callback(self.response)
        else:
            self.deferred.errback(reason)

def http_request_full(url, data=None, headers={}, method='POST'):
    agent = Agent(reactor)
    d = agent.request(method,
                      url,
                      mkheaders(headers),
                      StringProducer(data) if data else None)

    def handle_response(response):
        return SimplishReceiver(response).deferred

    d.addCallback(handle_response)
    return d

def mkheaders(headers):
    """
    Turn a dict of HTTP headers into an instance of Headers.

    Twisted expects a list of values, not a single value. We should
    support both.
    """
    raw_headers = {}
    for k, v in headers.iteritems():
        if isinstance(v, basestring):
            v = [v]
        raw_headers[k] = v
    return Headers(raw_headers)

def http_request(url, data, headers={}, method='POST'):
    d = http_request_full(url, data, headers=headers, method=method)
    return d.addCallback(lambda r: r.delivered_body)

def basic_auth_string(username, password):
    """
    Encode a username and password for use in an HTTP Basic Authentication
    header
    """
    b64 = base64.encodestring('%s:%s' % (username, password)).strip()
    return 'Basic %s' % b64

def normalize_msisdn(raw, country_code=''):
    # don't touch shortcodes
    if len(raw) <= 5:
        return raw

    raw = ''.join([c for c in str(raw) if c.isdigit() or c == '+'])
    if raw.startswith('00'):
        return '+' + raw[2:]
    if raw.startswith('0'):
        return '+' + country_code + raw[1:]
    if raw.startswith('+'):
        return raw
    if raw.startswith(country_code):
        return '+' + raw
    return raw

class StringProducer(object):
    """
    For various twisted.web mechanics we need a producer to produce
    content for HTTP requests, this is a helper class to quickly
    create a producer for a bit of content
    """
    implements(IBodyProducer)

    def __init__(self, body):
        self.body = body
        self.length = len(body)

    def startProducing(self, consumer):
        consumer.write(self.body)
        return succeed(None)

    def pauseProducing(self):
        pass

    def stopProducing(self):
        pass

def vumi_resource_path(path):
    """
    Return an absolute path to a Vumi package resource.

    Vumi package resources are found in the vumi.resources package.
    If the path is already absolute, it is returned unmodified.
    """
    if os.path.isabs(path):
        return path
    return pkg_resources.resource_filename("vumi.resources", path)

def load_class(module_name, class_name):
    """
    Load a class when given its module and its class name

    >>> load_class('vumi.workers.example','ExampleWorker') # doctest: +ELLIPSIS
    <class vumi.workers.example.ExampleWorker at ...>
    >>>

    """
    mod = import_module(module_name)
    return getattr(mod, class_name)

def load_class_by_string(class_path):
    """
    Load a class when given its full name, including modules in python
    dot notation

    >>> cls = 'vumi.workers.example.ExampleWorker'
    >>> load_class_by_string(cls) # doctest: +ELLIPSIS
    <class vumi.workers.example.ExampleWorker at ...>
    >>>

    """
    parts = class_path.split('.')
    module_name = '.'.join(parts[:-1])
    class_name = parts[-1]
    return load_class(module_name, class_name)

def filter_options_on_prefix(options, prefix, delimiter='-'):
    """
    splits an options dict based on key prefixes

    >>> filter_options_on_prefix({'foo-bar-1': 'ok'}, 'foo')
    {'bar-1': 'ok'}
    >>>

    """
    return dict((key.split(delimiter, 1)[1], value)
                for key, value in options.items()
                if key.startswith(prefix))

def get_first_word(content, delimiter=' '):
    """
    splits a string to get the first word

    >>>get_first_word('KEYWORD rest of message')
    'KEYWORD'
    >>>

    """
    return (content or '').split(delimiter)[0]

def cleanup_msisdn(number, country_code):
    number = re.sub('\+', '', number)
    number = re.sub('^0', country_code, number)
    return number

def get_operator_name(msisdn, mapping):
    for key, value in mapping.items():
        if msisdn.startswith(str(key)):
            if isinstance(value, dict):
                return get_operator_name(msisdn, value)
            return value
    return 'UNKNOWN'

def get_operator_number(msisdn, country_code, mapping, numbers):
    msisdn = cleanup_msisdn(msisdn, country_code)
    operator = get_operator_name(msisdn, mapping)
    number = numbers.get(operator)
    return number

def safe_routing_key(routing_key):
    """
    >>> safe_routing_key(u'*32323#')
    u's32323h'
    >>>

    """
    return reduce(lambda r_key, kv: r_key.replace(*kv),
                    [('*', 's'), ('#', 'h')], routing_key)

### SAMPLE CONFIG PARAMETERS - REPLACE 'x's IN OPERATOR_NUMBER

"""
COUNTRY_CODE: "27"

OPERATOR_NUMBER:
    VODACOM: "2782xxxxxxxxxxx"
    MTN: "2783xxxxxxxxxxx"
    CELLC: "2784xxxxxxxxxxx"
    VIRGIN: ""
    8TA: ""
    UNKNOWN: ""

OPERATOR_PREFIX:
    2771:
        27710: MTN
        27711: VODACOM
        27712: VODACOM
        27713: VODACOM
        27714: VODACOM
        27715: VODACOM
        27716: VODACOM
        27717: MTN
        27719: MTN

    2772: VODACOM
    2773: MTN
    2774:
        27740: CELLC
        27741: VIRGIN
        27742: CELLC
        27743: CELLC
        27744: CELLC
        27745: CELLC
        27746: CELLC
        27747: CELLC
        27748: CELLC
        27749: CELLC

    2776: VODACOM
    2778: MTN
    2779: VODACOM
    2781:
        27811: 8TA
        27812: 8TA
        27813: 8TA
        27814: 8TA

    2782: VODACOM
    2783: MTN
    2784: CELLC

"""
<commit_msg>complet docstring for get_first_word function<commit_after># -*- test-case-name: vumi.tests.test_utils -*-

import os.path
import re
import sys
import base64

import pkg_resources
from zope.interface import implements
from twisted.internet import defer
from twisted.internet import reactor, protocol
from twisted.internet.defer import succeed
from twisted.web.client import Agent, ResponseDone
from twisted.web.http_headers import Headers
from twisted.web.iweb import IBodyProducer
from twisted.web.http import PotentialDataLoss

def import_module(name):
    """
    This is a simpler version of `importlib.import_module` and does
    not support relative imports.

    It's here so that we can avoid using importlib and not have to
    juggle different deps between Python versions.
    """
    __import__(name)
    return sys.modules[name]

class SimplishReceiver(protocol.Protocol):
    def __init__(self, response):
        self.deferred = defer.Deferred()
        self.response = response
        self.response.delivered_body = ''
        if response.code == 204:
            self.deferred.callback(self.response)
        else:
            response.deliverBody(self)

    def dataReceived(self, data):
        self.response.delivered_body += data

    def connectionLost(self, reason):
        if reason.check(ResponseDone):
            self.deferred.callback(self.response)
        elif reason.check(PotentialDataLoss):
            # This is only (and always!) raised if we have an HTTP 1.0 request
            # with no Content-Length.
            # See http://twistedmatrix.com/trac/ticket/4840 for sadness.
            #
            # We ignore this and treat the call as success. If we care about
            # checking for potential data loss, we should do that in all cases
            # rather than trying to figure out if we might need to.
            self.deferred.callback(self.response)
        else:
            self.deferred.errback(reason)

def http_request_full(url, data=None, headers={}, method='POST'):
    agent = Agent(reactor)
    d = agent.request(method,
                      url,
                      mkheaders(headers),
                      StringProducer(data) if data else None)

    def handle_response(response):
        return SimplishReceiver(response).deferred

    d.addCallback(handle_response)
    return d

def mkheaders(headers):
    """
    Turn a dict of HTTP headers into an instance of Headers.

    Twisted expects a list of values, not a single value. We should
    support both.
    """
    raw_headers = {}
    for k, v in headers.iteritems():
        if isinstance(v, basestring):
            v = [v]
        raw_headers[k] = v
    return Headers(raw_headers)

def http_request(url, data, headers={}, method='POST'):
    d = http_request_full(url, data, headers=headers, method=method)
    return d.addCallback(lambda r: r.delivered_body)

def basic_auth_string(username, password):
    """
    Encode a username and password for use in an HTTP Basic Authentication
    header
    """
    b64 = base64.encodestring('%s:%s' % (username, password)).strip()
    return 'Basic %s' % b64

def normalize_msisdn(raw, country_code=''):
    # don't touch shortcodes
    if len(raw) <= 5:
        return raw

    raw = ''.join([c for c in str(raw) if c.isdigit() or c == '+'])
    if raw.startswith('00'):
        return '+' + raw[2:]
    if raw.startswith('0'):
        return '+' + country_code + raw[1:]
    if raw.startswith('+'):
        return raw
    if raw.startswith(country_code):
        return '+' + raw
    return raw

class StringProducer(object):
    """
    For various twisted.web mechanics we need a producer to produce
    content for HTTP requests, this is a helper class to quickly
    create a producer for a bit of content
    """
    implements(IBodyProducer)

    def __init__(self, body):
        self.body = body
        self.length = len(body)

    def startProducing(self, consumer):
        consumer.write(self.body)
        return succeed(None)

    def pauseProducing(self):
        pass

    def stopProducing(self):
        pass

def vumi_resource_path(path):
    """
    Return an absolute path to a Vumi package resource.

    Vumi package resources are found in the vumi.resources package.
    If the path is already absolute, it is returned unmodified.
    """
    if os.path.isabs(path):
        return path
    return pkg_resources.resource_filename("vumi.resources", path)

def load_class(module_name, class_name):
    """
    Load a class when given its module and its class name

    >>> load_class('vumi.workers.example','ExampleWorker') # doctest: +ELLIPSIS
    <class vumi.workers.example.ExampleWorker at ...>
    >>>

    """
    mod = import_module(module_name)
    return getattr(mod, class_name)

def load_class_by_string(class_path):
    """
    Load a class when given its full name, including modules in python
    dot notation

    >>> cls = 'vumi.workers.example.ExampleWorker'
    >>> load_class_by_string(cls) # doctest: +ELLIPSIS
    <class vumi.workers.example.ExampleWorker at ...>
    >>>

    """
    parts = class_path.split('.')
    module_name = '.'.join(parts[:-1])
    class_name = parts[-1]
    return load_class(module_name, class_name)

def filter_options_on_prefix(options, prefix, delimiter='-'):
    """
    splits an options dict based on key prefixes

    >>> filter_options_on_prefix({'foo-bar-1': 'ok'}, 'foo')
    {'bar-1': 'ok'}
    >>>

    """
    return dict((key.split(delimiter, 1)[1], value)
                for key, value in options.items()
                if key.startswith(prefix))

def get_first_word(content, delimiter=' '):
    """
    splits a string to get the first word

    >>>get_first_word('KEYWORD rest of message')
    'KEYWORD'
    >>>

    :param str content:
        Content from which the word will be retrive.

    :param str delimiter
        Delimiter identify the string which is following the returned value.
        In case the delimiter is not present in the content, the all content
        is returned.
    """
    return (content or '').split(delimiter)[0]

def cleanup_msisdn(number, country_code):
    number = re.sub('\+', '', number)
    number = re.sub('^0', country_code, number)
    return number

def get_operator_name(msisdn, mapping):
    for key, value in mapping.items():
        if msisdn.startswith(str(key)):
            if isinstance(value, dict):
                return get_operator_name(msisdn, value)
            return value
    return 'UNKNOWN'

def get_operator_number(msisdn, country_code, mapping, numbers):
    msisdn = cleanup_msisdn(msisdn, country_code)
    operator = get_operator_name(msisdn, mapping)
    number = numbers.get(operator)
    return number

def safe_routing_key(routing_key):
    """
    >>> safe_routing_key(u'*32323#')
    u's32323h'
    >>>

    """
    return reduce(lambda r_key, kv: r_key.replace(*kv),
                    [('*', 's'), ('#', 'h')], routing_key)

### SAMPLE CONFIG PARAMETERS - REPLACE 'x's IN OPERATOR_NUMBER

"""
COUNTRY_CODE: "27"

OPERATOR_NUMBER:
    VODACOM: "2782xxxxxxxxxxx"
    MTN: "2783xxxxxxxxxxx"
    CELLC: "2784xxxxxxxxxxx"
    VIRGIN: ""
    8TA: ""
    UNKNOWN: ""

OPERATOR_PREFIX:
    2771:
        27710: MTN
        27711: VODACOM
        27712: VODACOM
        27713: VODACOM
        27714: VODACOM
        27715: VODACOM
        27716: VODACOM
        27717: MTN
        27719: MTN

    2772: VODACOM
    2773: MTN
    2774:
        27740: CELLC
        27741: VIRGIN
        27742: CELLC
        27743: CELLC
        27744: CELLC
        27745: CELLC
        27746: CELLC
        27747: CELLC
        27748: CELLC
        27749: CELLC

    2776: VODACOM
    2778: MTN
    2779: VODACOM
    2781:
        27811: 8TA
        27812: 8TA
        27813: 8TA
        27814: 8TA

    2782: VODACOM
    2783: MTN
    2784: CELLC

"""
<|endoftext|>


<commit_before># coding=utf8

import sublime
import sublime_plugin

from .display.T3SViews import T3SVIEWS
from .display.Completion import COMPLETION
from .display.Errors import ERRORS
from .display.ErrorsHighlighter import ERRORSHIGHLIGHTER
from .system.Files import FILES
from .system.Liste import LISTE, get_root
from .system.Processes import PROCESSES
from .system.Settings import SETTINGS
from .Tss import TSS
from .Utils import debounce, is_ts, is_dts, read_file, get_file_infos, ST3, \
				   Debug, max_calls, run_command_on_any_ts_view, file_exists

# ------------------------------------------- INIT ------------------------------------------ #
c = [0]

@max_calls(name='main init')
def init(view):
	c[0] = c[0] + 1
	if c[0] == 10:
		pass #import spdb ; spdb.start()

	if not is_ts(view) or is_dts(view):
		return
	if read_file(view.file_name()) is None:
		return

	root = SETTINGS.get_root(view)
	if root == 'no_ts' or root == None: return

	if not file_exists(root):
		sublime.status_message("Typescript root file %s not found!" % root)
		print("T3S: Typescript root file %s not found! Update your .sublime-project file." % root)
		return

	filename = view.file_name()
	if PROCESSES.is_initialized(root):
		filename, num_lines, content = get_file_infos(view)
		if LISTE.has(filename):
			TSS.update(filename, num_lines, content)
		else:
			FILES.add(root, filename)
			TSS.add(root, filename, num_lines, content)

		view.run_command('typescript_update_structure')

	elif not PROCESSES.initialisation_started(root):
		FILES.add(root,filename)
		if filename != root:
			FILES.add(root,root)
		TSS.addEventListener('init', root, on_init)
		TSS.addEventListener('kill', root, on_kill)
		TSS.init(root)
		view.settings().set('auto_complete', SETTINGS.get("auto_complete", root))
		view.settings().set('extensions', ['ts'])

@max_calls()
def on_init(root):
	TSS.removeEventListener('init', root, on_init)
	FILES.init(root, on_files_loaded)

@max_calls()
def on_files_loaded():
	# we don't know if a ts view is activated, start conditions
	run_command_on_any_ts_view('typescript_update_structure', {"force": True})
	run_command_on_any_ts_view('typescript_recalculate_errors')

@max_calls()
def on_kill(root):
	TSS.removeEventListener('kill', root, on_kill)
	FILES.remove_by_root(root)
	ERRORS.on_close_typescript_project(root)
	T3SVIEWS.hide_all()

# ----------------------------------------- LISTENERS ---------------------------------------- #

class TypescriptEventListener(sublime_plugin.EventListener):

	# CLOSE FILE
	@max_calls(name='Listener.on_close')
	def on_close(self, view):

		if is_ts(view) and not is_dts(view):
			filename = view.file_name()
			if ST3: TSS.kill(filename)
			else: sublime.set_timeout(lambda:TSS.kill(filename),300)

	# FILE ACTIVATED
	@max_calls()
	def on_activated(self, view):
		init(view)

	# ON CLONED FILE
	@max_calls()
	def on_clone(self, view):
		init(view)

	# ON SAVE
	@max_calls()
	def on_post_save(self, view):
		if not is_ts(view):
			return

		filename, num_lines, content = get_file_infos(view)
		if LISTE.has(filename):
			TSS.update(filename, num_lines, content)
			FILES.update(filename, num_lines, content, True)

		view.run_command('typescript_update_structure', {"force": True})
		ERRORS.start_recalculation(view.file_name())

		if SETTINGS.get('build_on_save', get_root(filename)):
			sublime.active_window().run_command('typescript_build',{"characters":False})

	# ON CLICK
	@max_calls(name='listener.on_selection_modified')
	def on_selection_modified(self, view):
		if not is_ts(view) or is_dts(view):
			return

		ERRORSHIGHLIGHTER.display_error_in_status_if_cursor(view)
		view.erase_regions('typescript-definition')
		view.erase_regions('typescript-error-hint')

	# ON VIEW MODIFIED
	@max_calls()
	def on_modified(self, view):
		if view.is_loading():
			return
		if not is_ts(view) or is_dts(view):
			return

		filename, num_lines, content = get_file_infos(view)
		if LISTE.has(filename):
			TSS.update(filename, num_lines, content)
			FILES.update(filename, num_lines, content)

		view.run_command('typescript_update_structure', {"force": True})
		COMPLETION.trigger(view, TSS)

		if not SETTINGS.get('error_on_save_only', get_root(filename)):
			ERRORS.start_recalculation(view.file_name())

	# ON QUERY COMPLETION
	def on_query_completions(self, view, prefix, locations):
		pos = view.sel()[0].begin()
		(line, col) = view.rowcol(pos)
		Debug('autocomplete', "on_query_completions(), sublime wants to see the results, cursor currently at %i , %i (enabled: %s, items: %i)" % (line+1, col+1, COMPLETION.enabled_for['viewid'], len(COMPLETION.get_list()) ) )
		if is_ts(view) and not is_dts(view):
			if COMPLETION.enabled_for['viewid'] == view.id():
				COMPLETION.enabled_for['viewid'] = -1 # receive only once
				return (COMPLETION.get_list(), sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS)

	# ON QUERY CONTEXT (execute commandy only on .ts files)
	def on_query_context(self, view, key, operator, operand, match_all):
		if key == "T3S":
			view = sublime.active_window().active_view()
			return is_ts(view)

<commit_msg>fix little bug<commit_after># coding=utf8

import sublime
import sublime_plugin

from .display.T3SViews import T3SVIEWS
from .display.Completion import COMPLETION
from .display.Errors import ERRORS
from .display.ErrorsHighlighter import ERRORSHIGHLIGHTER
from .system.Files import FILES
from .system.Liste import LISTE, get_root
from .system.Processes import PROCESSES
from .system.Settings import SETTINGS
from .Tss import TSS
from .Utils import debounce, is_ts, is_dts, read_file, get_file_infos, ST3, \
				   Debug, max_calls, run_command_on_any_ts_view, file_exists

# ------------------------------------------- INIT ------------------------------------------ #
c = [0]

@max_calls(name='main init')
def init(view):
	c[0] = c[0] + 1
	if c[0] == 10:
		pass #import spdb ; spdb.start()

	if not is_ts(view) or is_dts(view):
		return
	if read_file(view.file_name()) is None:
		return

	root = SETTINGS.get_root(view)
	if root == 'no_ts' or root == None: return

	if not file_exists(root):
		sublime.status_message("Typescript root file %s not found!" % root)
		print("T3S: Typescript root file %s not found! Update your .sublime-project file." % root)
		return

	filename = view.file_name()
	if PROCESSES.is_initialized(root):
		filename, num_lines, content = get_file_infos(view)
		if LISTE.has(filename):
			TSS.update(filename, num_lines, content)
		else:
			FILES.add(root, filename)
			TSS.add(root, filename, num_lines, content)

		view.run_command('typescript_update_structure')

	elif not PROCESSES.initialisation_started(root):
		FILES.add(root,filename)
		if filename != root:
			FILES.add(root,root)
		TSS.addEventListener('init', root, on_init)
		TSS.addEventListener('kill', root, on_kill)
		TSS.init(root)
		view.settings().set('auto_complete', SETTINGS.get("auto_complete", root))
		view.settings().set('extensions', ['ts'])

@max_calls()
def on_init(root):
	TSS.removeEventListener('init', root, on_init)
	FILES.init(root, on_files_loaded)

@max_calls()
def on_files_loaded():
	# we don't know if a ts view is activated, start conditions
	run_command_on_any_ts_view('typescript_update_structure', {"force": True})
	run_command_on_any_ts_view('typescript_recalculate_errors')

@max_calls()
def on_kill(root):
	TSS.removeEventListener('kill', root, on_kill)
	FILES.remove_by_root(root)
	ERRORS.on_close_typescript_project(root)
	T3SVIEWS.hide_all()

# ----------------------------------------- LISTENERS ---------------------------------------- #

class TypescriptEventListener(sublime_plugin.EventListener):

	# CLOSE FILE
	@max_calls(name='Listener.on_close')
	def on_close(self, view):

		if is_ts(view) and not is_dts(view):
			filename = view.file_name()
			if ST3: TSS.kill(filename)
			else: sublime.set_timeout(lambda:TSS.kill(filename),300)

	# FILE ACTIVATED
	@max_calls()
	def on_activated(self, view):
		init(view)

	# ON CLONED FILE
	@max_calls()
	def on_clone(self, view):
		init(view)

	# ON SAVE
	@max_calls()
	def on_post_save(self, view):
		if not is_ts(view):
			return

		filename, num_lines, content = get_file_infos(view)
		if LISTE.has(filename):
			TSS.update(filename, num_lines, content)
			FILES.update(filename, num_lines, content, True)

		view.run_command('typescript_update_structure', {"force": True})
		ERRORS.start_recalculation(view.file_name())

		if get_root(filename) and SETTINGS.get('build_on_save', get_root(filename)):
			sublime.active_window().run_command('typescript_build',{"characters":False})

	# ON CLICK
	@max_calls(name='listener.on_selection_modified')
	def on_selection_modified(self, view):
		if not is_ts(view) or is_dts(view):
			return

		ERRORSHIGHLIGHTER.display_error_in_status_if_cursor(view)
		view.erase_regions('typescript-definition')
		view.erase_regions('typescript-error-hint')

	# ON VIEW MODIFIED
	@max_calls()
	def on_modified(self, view):
		if view.is_loading():
			return
		if not is_ts(view) or is_dts(view):
			return

		filename, num_lines, content = get_file_infos(view)
		if LISTE.has(filename):
			TSS.update(filename, num_lines, content)
			FILES.update(filename, num_lines, content)

		view.run_command('typescript_update_structure', {"force": True})
		COMPLETION.trigger(view, TSS)

		if get_root(filename) and not SETTINGS.get('error_on_save_only', get_root(filename)):
			ERRORS.start_recalculation(view.file_name())

	# ON QUERY COMPLETION
	def on_query_completions(self, view, prefix, locations):
		pos = view.sel()[0].begin()
		(line, col) = view.rowcol(pos)
		Debug('autocomplete', "on_query_completions(), sublime wants to see the results, cursor currently at %i , %i (enabled: %s, items: %i)" % (line+1, col+1, COMPLETION.enabled_for['viewid'], len(COMPLETION.get_list()) ) )
		if is_ts(view) and not is_dts(view):
			if COMPLETION.enabled_for['viewid'] == view.id():
				COMPLETION.enabled_for['viewid'] = -1 # receive only once
				return (COMPLETION.get_list(), sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS)

	# ON QUERY CONTEXT (execute commandy only on .ts files)
	def on_query_context(self, view, key, operator, operand, match_all):
		if key == "T3S":
			view = sublime.active_window().active_view()
			return is_ts(view)

<|endoftext|>


<commit_before>#
# Copyright (c) 2006-2009 rPath, Inc
# All rights reserved
#
import time
import os
import md5
import simplejson
import re
import socket

from gettext import gettext as _
import raa
import raa.web
import logging
log = logging.getLogger('raa.web')

from raa import rpath_error
from raa import constants
from raa.db.data import RDT_BOOL, RDT_INT, RDT_JSON, RDT_STRING
from raa.db import schedule
from raa.db import wizardrun
from raa.modules import raawebplugin
from raa.modules.raawebplugin import rAAWebPlugin
from raa.lib import validate

from mint import config
from mint import helperfuncs

from rPath.rbasetup import lib

ipAddrMatch = re.compile('^[0-9.]+$')
userNameMatch = re.compile('^[A-Za-z0-9._-]+$')
namespaceMatch = re.compile('^[A-Za-z0-9-]+$')

def validateNamespace(n):
    if not namespaceMatch.match(n):
        return "The namespace may only contain alphanumeric characters and hyphens."
    return ""

class rBASetup(rAAWebPlugin):
    """
    Represents the web side of the plugin.
    """
    # Name to be displayed in the side bar.
    displayName = _("rBuilder Setup")

    # Name to be displayed on mouse over in the side bar.
    tooltip = _("Plugin for setting up the rBuilder Appliance")

    wizardSerial = 1

    def initPlugin(self):
        self.checkSkipWizard()

    def checkSkipWizard(self):
        # If the system is already configured and this plug-in hasn't been run
        # in the wizard yet (i.e. migration) mark it as done.  We only want to
        # run in the wizard during install.
        isConfigured, cfg = lib.getRBAConfiguration()
        if isConfigured:
            # this initializes the pluginId dict which we need
            raa.modules.helper.getWebPluginIdDict()
            wizardRun = raa.web.getWebRoot().wizardRun

            # has wizard already run?
            if wizardRun.getLastRunTime(self.taskId, self.wizardSerial) is not None:
                return

            wizardRun.resetSession([(self.taskId, self.wizardSerial)])
            self._wizardDone()
            wizardRun.commit()

    def _getFirstTimeSetupStatus(self):
        # Get some status here
        schedId = self.getPropertyValue('FTS_SCHEDID', -1)
        if schedId == -1:
            return dict(schedId=-1, errors=['No setup task is running.'])
        status = raa.web.getWebRoot().callGetStatus(schedId)
        ret = dict(schedId=status['schedId'],
                   status=status['status'],
                   statusmsg=status['statusmsg'])

        return ret

    def _normalizeFormKwargs(self, kwargsFromForm):
        """
        Takes a pile of kwargs from the form post and normalizes it,
        stripping leading and trailing whitespace and converting
        booleans into the Boolean type.
        """
        boolean_options = ('requireSigs', 'configured', 'allowNamespaceChange')
        normalizedOptions = dict()

        for k, v in kwargsFromForm.iteritems():
            newV = v.strip()
            if k in boolean_options:
                normalizedOptions[k] = bool(int(str(v)))
            else:
                if newV:
                    normalizedOptions[k] = newV

        return normalizedOptions

    def _validateSetupForm(self, options):
        """
        Takes a list of options and validates them.
        Returns a list of errors (empty list if OK).
        """
        errorList = list()

        if not options['configured']:
            # validate the hostname
            if 'hostName' not in options:
                errorList.append("You must specify a fully-qualified hostname")
            elif ipAddrMatch.match(options['hostName']):
                errorList.append("The hostname appears to be an IP address.")
            elif not validate.validateFqdn(options['hostName']):
                errorList.append("The hostname is invalid.")

            # validate the site domain name
            if 'projectDomainName' not in options:
                errorList.append("You must specify a domain name for this installation.")
            elif ipAddrMatch.match(options['projectDomainName']):
                errorList.append("The domain name appears to be an IP address.")
            elif not validate.validateFqdn(options['projectDomainName']):
                errorList.append("The domain name is invalid.")

            # validate the username
            if 'new_username' not in options:
                errorList.append("You must enter a username to be created")
            elif not userNameMatch.match(options['new_username']):
                errorList.append("Your username must contain only alphnumeric characters, periods, hyphens, and underscores")

            # validate the email address
            if 'new_email' not in options:
                errorList.append("You must enter an administrator email address")

            # validate password
            if  ('new_password' not in options or 'new_password2' not in options):
                errorList.append("You must enter initial passwords")
            elif options['new_password'] != options['new_password2']:
                errorList.append("Passwords must match")
            elif options['new_password'].find('#') != -1:
                errorList.append("Passwords must not contain a #")

        if options.get('externalPasswordURL'):
            # XXX Removing the validation for this for now;
            #     it was more necessary when you could lock yourself
            #     out of rBuilder if you messed it up.
            #userAuth = netauth.UserAuthorization( \
            #    None, pwCheckUrl = options['externalPasswordURL'],
            #    cacheTimeout = int(options.get('authCacheTimeout')))
            #if self.auth.admin and not userAuth._checkPassword( \
            #    self.auth.username, None, None, self.auth.token[1]):
            #    errorList.append('Username: %s was not accepted by: %s' % \
            #                  (self.auth.username,
            #                   options['externalPasswordURL']))
            #if options.get('new_username') and \
            #       not userAuth._checkPassword(options.get('new_username'),
            #                                   None, None,
            #                                   options.get('new_password')):
            #    errorList.append('Username: %s was not accepted by: %s' % \
            #                  (options.get('new_username'),
            #                   options.get('new_password')))
            pass

        # validate the namespace
        if options.get('allowNamespaceChange'):
            namespaceValid = False
            if 'namespace' not in options:
                errorList.append("You must specify a namespace for this installation.")
            else:
                # returns text explanation if invalid
                errorString = validateNamespace(options['namespace'])
                if errorString:
                    errorList.append(errorString)

        return errorList

    @raa.web.expose(template="rPath.rbasetup.index")
    def index(self):
        # check to see if we're mid-setup

        status = self._getFirstTimeSetupStatus()
        if status['schedId'] != -1:
            raa.web.raiseHttpRedirect('/rbasetup/rBASetup/firstTimeSetup')

        # Get the configuration from the backend
        isConfigured, configurableOptions = lib.getRBAConfiguration()

        # if the namespace has already been set, don't allow them to change it
        # FIXME this needs to be changed - implemented for RBL-2905.
        dflNamespace = config.MintConfig().namespace
        allowNamespaceChange = not isConfigured or (configurableOptions['namespace'] == dflNamespace)

        # Backend returns Unicode for keys, which is suboptimal for Kid
        sanitizedConfigurableOptions = \
                dict([(str(k),v) for k, v in configurableOptions.iteritems()])

        # Return the hostname from the request if it's not set
        if not sanitizedConfigurableOptions['hostName'] or \
           ipAddrMatch.match(sanitizedConfigurableOptions['hostName']):
            fqdn = raa.web.getRequestHostname()
            ipAddr = None
            if ipAddrMatch.match(fqdn):
                # user is using IP address to access the rbuilder
                # suggest a hostname instead
                ipAddr = fqdn
                fqdn = os.uname()[1]
            if fqdn.find('.') == -1:
                # Maybe we're better off doing a reverse lookup
                try:
                    if not ipAddr:
                        ipAddr = socket.gethostbyname(fqdn)
                    fqdn = socket.gethostbyaddr(ipAddr)[0]
                except:
                    # nothing is fully-qualified.  :-(
                    pass
            bits = fqdn.split('.')
            sanitizedConfigurableOptions['hostName'] = '.'.join(bits)
            try:
                sanitizedConfigurableOptions['siteDomainName'] = '.'.join(bits[1:])
            except IndexError:
                sanitizedConfigurableOptions['siteDomainName'] = ''
            sanitizedConfigurableOptions['projectDomainName'] = sanitizedConfigurableOptions['siteDomainName']
            if not isConfigured and len(bits) > 1:
                sanitizedConfigurableOptions['namespace'] = bits[-2]

        return dict(isConfigured=isConfigured,
                allowNamespaceChange=allowNamespaceChange,
                **sanitizedConfigurableOptions)

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def saveConfig(self, *args, **kwargs):
        """
        Called upon posting the setup form.
        """
        # Normalize kwargs into a minimal set of options
        normalizedOptions = self._normalizeFormKwargs(kwargs)
        isConfigured, cfg = lib.getRBAConfiguration()

        # Validate the setup form
        errorList = self._validateSetupForm(normalizedOptions)
        if errorList:
            return dict(errors=errorList)

        # split hostname & siteDomainName
        if normalizedOptions.has_key('hostName'):
            hostname = normalizedOptions['hostName'].split('.')
            normalizedOptions['hostName'] = hostname[0]
            normalizedOptions['siteDomainName'] = '.'.join(hostname[1:])

        # Call backend to save generated file
        try:
            result = self.callBackend('updateRBAConfig', normalizedOptions)
            if result.has_key('errors'):
                return dict(errors=result['errors'])
            else:
                ret = dict(message="rBuilder Configuration saved.")
        except Exception, e:
            return dict(errors=["Failed to save rBuilder Configuration: %s" % repr(e)])

        # If this is a first time setup, we must kick off a redirect to do
        # post setup processing.
        if not isConfigured:
            username = raa.identity.current.user_name
            user = raa.identity.current_provider.authenticated_identity(username)
            raa.identity.current_provider.set_password(user,
                    normalizedOptions['new_password'])
            try:
                # do a simple check to catch the case where the user
                # did not fully qualify their hostname
                currentHostname = os.uname()[1]
                if currentHostname != '.'.join(hostname) and currentHostname == hostname[0]:
                    netCfg = self.plugins['/configure/Network'].index()
                    gatewayDHCP = False
                    if netCfg['host_gateway'] == '':
                        gatewayDHCP = True
                    log.info('saving new hostname')
                    self.plugins['/configure/Network']._saveGeneral(
                         netCfg['host_usesdhcp'],
                         '.'.join(hostname),
                         netCfg['dns_dnsDomain'],
                         netCfg['dns_dnsServer'],
                         gatewayDHCP,
                         netCfg['host_gateway'],
                         netCfg['host_gatewaydev'],
                         netCfg['dns_dnsdhcp'],
                         )
                    log.info('new hostname saved')
            except Exception, e:
                log.error('Hostname reset failed.  Continuing anyway...')
            # Kick off the first time processing
            sched = schedule.ScheduleOnce(time.time() + 1)
            schedId = self.callBackendAsync(sched, 'firstTimeSetup', normalizedOptions)
            self.setPropertyValue('FTS_SCHEDID', schedId, RDT_INT)
            # raa.web.raiseHttpRedirect('/rbasetup/rBASetup/firstTimeSetup')
            return True
        else:
            return ret

        return dict(errors=['An unexpected condition occurred'])

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def callGetStatus(self, schedId):
        return raa.web.getWebRoot().callGetStatus(schedId)

    @raa.web.expose(allow_json=True)
    def retryFirstTimeSetup(self):
        currentStatus = self._getFirstTimeSetupStatus()
        if currentStatus['status'] == constants.TASK_FATAL_ERROR or \
            (currentStatus['status'] == constants.TASK_SUCCESS and
             not self.getPropertyValue('FINALIZED', False)):
            sched = schedule.ScheduleOnce(time.time() + 1)
            schedId = self.callBackendAsync(sched, 'firstTimeSetup', dict(retry=True))
            self.deletePropertyValue('FTS_SCHEDID')
            self.setPropertyValue('FTS_SCHEDID', schedId, RDT_INT)
            return self._getFirstTimeSetupStatus()
        else:
            log.error('not retrying FTS: status is %s' % currentStatus['status'] )
        return

    @raa.web.expose(allow_xmlrpc=True, allow_json=True, template="rPath.rbasetup.firstTimeSetup")
    def firstTimeSetup(self):
        status = self._getFirstTimeSetupStatus()
        if status['schedId'] == -1:
            raa.web.raiseHttpRedirect('/rbasetup/rBASetup/')
        return status

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def getFirstTimeSetupStatus(self):
        result = self._getFirstTimeSetupStatus()
        result.update(reload=False)
        if result['schedId'] == -1:
            return result

        # if the task is complete, get the return values
        if result['status'] == constants.TASK_SUCCESS:
            try:
                isException, ret = raa.web.getWebRoot().simpleTasks.getResult(result['schedId'])
                if isException:
                    try:
                        result['errors'] = " ".join( [ x.encode('utf-8') for x in ret ])
                    except:
                        result['errors'] = " ".join( [ x for x in ret ] )
                    result['status'] = constants.TASK_FATAL_ERROR
                    result['statusmsg'] = ''
                else:
                    result['statusmsg'] = ret.get('message', '')
                    result['errors'] = ret.get('errors', '')
                    if result['errors'] != '':
                        result['status'] = constants.TASK_FATAL_ERROR
            except Exception, e:
                # The task no longer exists
                finalized = self.getPropertyValue('FINALIZED', False)
                if not finalized:
                    # assume that it failed.  rerunning should not hurt.
                    result['status'] = constants.TASK_FATAL_ERROR
                result['statusmsg'] = ''
                result['errors'] = ''

            if isinstance(result['errors'], list):
                result['errors'] = "\n".join(result['errors'])
        elif result['status'] == constants.TASK_FATAL_ERROR:
            # NOTE: elif here is not the same as the following if
            result['errors'] = result['statusmsg']
            result['statusmsg'] = ''

        if result['status'] == constants.TASK_FATAL_ERROR:
            if raa.web.inWizardMode():
                self.wizardDone()
                result.update(reload=True)

        return result

    @raa.web.expose(allow_xmlrpc=True)
    @raa.web.require(raa.authorization.LocalhostOnly())
    def setWizardDone(self):
        log.info('setting wizard done')
        try:
            self._wizardDone()
        except Exception, e:
            log.error('error setting wizard done')
        return dict(message='successfully set wizard done')

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    @raa.web.require(raa.authorization.LocalhostOnly())
    def setNewEntitlement(self, key):
        self.plugins['/configure/Entitlements'].doSaveKey(key)
        return True

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def finalize(self):
        # mark finalized
        self.setPropertyValue('FINALIZED', True, RDT_BOOL)
        self.deletePropertyValue('FTS_SCHEDID')

        # mark done in the wizard
        self.wizardDone()

        # redirect to the rbuilder itself
        fqdn = raa.web.getRequestHostname()
        raa.web.raiseHttpRedirect("http://%s/" % (fqdn,))
<commit_msg>Change First-time setup redirect to populate login username (RBL-5563)<commit_after>#
# Copyright (c) 2006-2009 rPath, Inc
# All rights reserved
#
import time
import os
import md5
import simplejson
import re
import socket
import urllib

from gettext import gettext as _
import raa
import raa.web
import logging
log = logging.getLogger('raa.web')

from raa import rpath_error
from raa import constants
from raa.db.data import RDT_BOOL, RDT_INT, RDT_JSON, RDT_STRING
from raa.db import schedule
from raa.db import wizardrun
from raa.modules import raawebplugin
from raa.modules.raawebplugin import rAAWebPlugin
from raa.lib import validate

from mint import config
from mint import helperfuncs

from rPath.rbasetup import lib

ipAddrMatch = re.compile('^[0-9.]+$')
userNameMatch = re.compile('^[A-Za-z0-9._-]+$')
namespaceMatch = re.compile('^[A-Za-z0-9-]+$')

def validateNamespace(n):
    if not namespaceMatch.match(n):
        return "The namespace may only contain alphanumeric characters and hyphens."
    return ""

class rBASetup(rAAWebPlugin):
    """
    Represents the web side of the plugin.
    """
    # Name to be displayed in the side bar.
    displayName = _("rBuilder Setup")

    # Name to be displayed on mouse over in the side bar.
    tooltip = _("Plugin for setting up the rBuilder Appliance")

    wizardSerial = 1

    def initPlugin(self):
        self.checkSkipWizard()

    def checkSkipWizard(self):
        # If the system is already configured and this plug-in hasn't been run
        # in the wizard yet (i.e. migration) mark it as done.  We only want to
        # run in the wizard during install.
        isConfigured, cfg = lib.getRBAConfiguration()
        if isConfigured:
            # this initializes the pluginId dict which we need
            raa.modules.helper.getWebPluginIdDict()
            wizardRun = raa.web.getWebRoot().wizardRun

            # has wizard already run?
            if wizardRun.getLastRunTime(self.taskId, self.wizardSerial) is not None:
                return

            wizardRun.resetSession([(self.taskId, self.wizardSerial)])
            self._wizardDone()
            wizardRun.commit()

    def _getFirstTimeSetupStatus(self):
        # Get some status here
        schedId = self.getPropertyValue('FTS_SCHEDID', -1)
        if schedId == -1:
            return dict(schedId=-1, errors=['No setup task is running.'])
        status = raa.web.getWebRoot().callGetStatus(schedId)
        ret = dict(schedId=status['schedId'],
                   status=status['status'],
                   statusmsg=status['statusmsg'])

        return ret

    def _normalizeFormKwargs(self, kwargsFromForm):
        """
        Takes a pile of kwargs from the form post and normalizes it,
        stripping leading and trailing whitespace and converting
        booleans into the Boolean type.
        """
        boolean_options = ('requireSigs', 'configured', 'allowNamespaceChange')
        normalizedOptions = dict()

        for k, v in kwargsFromForm.iteritems():
            newV = v.strip()
            if k in boolean_options:
                normalizedOptions[k] = bool(int(str(v)))
            else:
                if newV:
                    normalizedOptions[k] = newV

        return normalizedOptions

    def _validateSetupForm(self, options):
        """
        Takes a list of options and validates them.
        Returns a list of errors (empty list if OK).
        """
        errorList = list()

        if not options['configured']:
            # validate the hostname
            if 'hostName' not in options:
                errorList.append("You must specify a fully-qualified hostname")
            elif ipAddrMatch.match(options['hostName']):
                errorList.append("The hostname appears to be an IP address.")
            elif not validate.validateFqdn(options['hostName']):
                errorList.append("The hostname is invalid.")

            # validate the site domain name
            if 'projectDomainName' not in options:
                errorList.append("You must specify a domain name for this installation.")
            elif ipAddrMatch.match(options['projectDomainName']):
                errorList.append("The domain name appears to be an IP address.")
            elif not validate.validateFqdn(options['projectDomainName']):
                errorList.append("The domain name is invalid.")

            # validate the username
            if 'new_username' not in options:
                errorList.append("You must enter a username to be created")
            elif not userNameMatch.match(options['new_username']):
                errorList.append("Your username must contain only alphnumeric characters, periods, hyphens, and underscores")

            # validate the email address
            if 'new_email' not in options:
                errorList.append("You must enter an administrator email address")

            # validate password
            if  ('new_password' not in options or 'new_password2' not in options):
                errorList.append("You must enter initial passwords")
            elif options['new_password'] != options['new_password2']:
                errorList.append("Passwords must match")
            elif options['new_password'].find('#') != -1:
                errorList.append("Passwords must not contain a #")

        if options.get('externalPasswordURL'):
            # XXX Removing the validation for this for now;
            #     it was more necessary when you could lock yourself
            #     out of rBuilder if you messed it up.
            #userAuth = netauth.UserAuthorization( \
            #    None, pwCheckUrl = options['externalPasswordURL'],
            #    cacheTimeout = int(options.get('authCacheTimeout')))
            #if self.auth.admin and not userAuth._checkPassword( \
            #    self.auth.username, None, None, self.auth.token[1]):
            #    errorList.append('Username: %s was not accepted by: %s' % \
            #                  (self.auth.username,
            #                   options['externalPasswordURL']))
            #if options.get('new_username') and \
            #       not userAuth._checkPassword(options.get('new_username'),
            #                                   None, None,
            #                                   options.get('new_password')):
            #    errorList.append('Username: %s was not accepted by: %s' % \
            #                  (options.get('new_username'),
            #                   options.get('new_password')))
            pass

        # validate the namespace
        if options.get('allowNamespaceChange'):
            namespaceValid = False
            if 'namespace' not in options:
                errorList.append("You must specify a namespace for this installation.")
            else:
                # returns text explanation if invalid
                errorString = validateNamespace(options['namespace'])
                if errorString:
                    errorList.append(errorString)

        return errorList

    @raa.web.expose(template="rPath.rbasetup.index")
    def index(self):
        # check to see if we're mid-setup

        status = self._getFirstTimeSetupStatus()
        if status['schedId'] != -1:
            raa.web.raiseHttpRedirect('/rbasetup/rBASetup/firstTimeSetup')

        # Get the configuration from the backend
        isConfigured, configurableOptions = lib.getRBAConfiguration()

        # if the namespace has already been set, don't allow them to change it
        # FIXME this needs to be changed - implemented for RBL-2905.
        dflNamespace = config.MintConfig().namespace
        allowNamespaceChange = not isConfigured or (configurableOptions['namespace'] == dflNamespace)

        # Backend returns Unicode for keys, which is suboptimal for Kid
        sanitizedConfigurableOptions = \
                dict([(str(k),v) for k, v in configurableOptions.iteritems()])

        # Return the hostname from the request if it's not set
        if not sanitizedConfigurableOptions['hostName'] or \
           ipAddrMatch.match(sanitizedConfigurableOptions['hostName']):
            fqdn = raa.web.getRequestHostname()
            ipAddr = None
            if ipAddrMatch.match(fqdn):
                # user is using IP address to access the rbuilder
                # suggest a hostname instead
                ipAddr = fqdn
                fqdn = os.uname()[1]
            if fqdn.find('.') == -1:
                # Maybe we're better off doing a reverse lookup
                try:
                    if not ipAddr:
                        ipAddr = socket.gethostbyname(fqdn)
                    fqdn = socket.gethostbyaddr(ipAddr)[0]
                except:
                    # nothing is fully-qualified.  :-(
                    pass
            bits = fqdn.split('.')
            sanitizedConfigurableOptions['hostName'] = '.'.join(bits)
            try:
                sanitizedConfigurableOptions['siteDomainName'] = '.'.join(bits[1:])
            except IndexError:
                sanitizedConfigurableOptions['siteDomainName'] = ''
            sanitizedConfigurableOptions['projectDomainName'] = sanitizedConfigurableOptions['siteDomainName']
            if not isConfigured and len(bits) > 1:
                sanitizedConfigurableOptions['namespace'] = bits[-2]

        return dict(isConfigured=isConfigured,
                allowNamespaceChange=allowNamespaceChange,
                **sanitizedConfigurableOptions)

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def saveConfig(self, *args, **kwargs):
        """
        Called upon posting the setup form.
        """
        # Normalize kwargs into a minimal set of options
        normalizedOptions = self._normalizeFormKwargs(kwargs)
        isConfigured, cfg = lib.getRBAConfiguration()

        # Validate the setup form
        errorList = self._validateSetupForm(normalizedOptions)
        if errorList:
            return dict(errors=errorList)

        # split hostname & siteDomainName
        if normalizedOptions.has_key('hostName'):
            hostname = normalizedOptions['hostName'].split('.')
            normalizedOptions['hostName'] = hostname[0]
            normalizedOptions['siteDomainName'] = '.'.join(hostname[1:])

        # Call backend to save generated file
        try:
            result = self.callBackend('updateRBAConfig', normalizedOptions)
            if result.has_key('errors'):
                return dict(errors=result['errors'])
            else:
                ret = dict(message="rBuilder Configuration saved.")
        except Exception, e:
            return dict(errors=["Failed to save rBuilder Configuration: %s" % repr(e)])

        # If this is a first time setup, we must kick off a redirect to do
        # post setup processing.
        if not isConfigured:
            username = raa.identity.current.user_name
            user = raa.identity.current_provider.authenticated_identity(username)
            raa.identity.current_provider.set_password(user,
                    normalizedOptions['new_password'])
            try:
                # do a simple check to catch the case where the user
                # did not fully qualify their hostname
                currentHostname = os.uname()[1]
                if currentHostname != '.'.join(hostname) and currentHostname == hostname[0]:
                    netCfg = self.plugins['/configure/Network'].index()
                    gatewayDHCP = False
                    if netCfg['host_gateway'] == '':
                        gatewayDHCP = True
                    log.info('saving new hostname')
                    self.plugins['/configure/Network']._saveGeneral(
                         netCfg['host_usesdhcp'],
                         '.'.join(hostname),
                         netCfg['dns_dnsDomain'],
                         netCfg['dns_dnsServer'],
                         gatewayDHCP,
                         netCfg['host_gateway'],
                         netCfg['host_gatewaydev'],
                         netCfg['dns_dnsdhcp'],
                         )
                    log.info('new hostname saved')
            except Exception, e:
                log.error('Hostname reset failed.  Continuing anyway...')
            # Kick off the first time processing
            sched = schedule.ScheduleOnce(time.time() + 1)
            schedId = self.callBackendAsync(sched, 'firstTimeSetup', normalizedOptions)
            self.setPropertyValue('FTS_SCHEDID', schedId, RDT_INT)
            # store the rBA Admin username, for use in other functions
            self.setPropertyValue('RBA_ADMIN', normalizedOptions['new_username'], RDT_INT)
            # raa.web.raiseHttpRedirect('/rbasetup/rBASetup/firstTimeSetup')
            return True
        else:
            return ret

        return dict(errors=['An unexpected condition occurred'])

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def callGetStatus(self, schedId):
        return raa.web.getWebRoot().callGetStatus(schedId)

    @raa.web.expose(allow_json=True)
    def retryFirstTimeSetup(self):
        currentStatus = self._getFirstTimeSetupStatus()
        if currentStatus['status'] == constants.TASK_FATAL_ERROR or \
            (currentStatus['status'] == constants.TASK_SUCCESS and
             not self.getPropertyValue('FINALIZED', False)):
            sched = schedule.ScheduleOnce(time.time() + 1)
            schedId = self.callBackendAsync(sched, 'firstTimeSetup', dict(retry=True))
            self.deletePropertyValue('FTS_SCHEDID')
            self.setPropertyValue('FTS_SCHEDID', schedId, RDT_INT)
            return self._getFirstTimeSetupStatus()
        else:
            log.error('not retrying FTS: status is %s' % currentStatus['status'] )
        return

    @raa.web.expose(allow_xmlrpc=True, allow_json=True, template="rPath.rbasetup.firstTimeSetup")
    def firstTimeSetup(self):
        status = self._getFirstTimeSetupStatus()
        if status['schedId'] == -1:
            raa.web.raiseHttpRedirect('/rbasetup/rBASetup/')
        return status

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def getFirstTimeSetupStatus(self):
        result = self._getFirstTimeSetupStatus()
        result.update(reload=False)
        if result['schedId'] == -1:
            return result

        # if the task is complete, get the return values
        if result['status'] == constants.TASK_SUCCESS:
            try:
                isException, ret = raa.web.getWebRoot().simpleTasks.getResult(result['schedId'])
                if isException:
                    try:
                        result['errors'] = " ".join( [ x.encode('utf-8') for x in ret ])
                    except:
                        result['errors'] = " ".join( [ x for x in ret ] )
                    result['status'] = constants.TASK_FATAL_ERROR
                    result['statusmsg'] = ''
                else:
                    result['statusmsg'] = ret.get('message', '')
                    result['errors'] = ret.get('errors', '')
                    if result['errors'] != '':
                        result['status'] = constants.TASK_FATAL_ERROR
            except Exception, e:
                # The task no longer exists
                finalized = self.getPropertyValue('FINALIZED', False)
                if not finalized:
                    # assume that it failed.  rerunning should not hurt.
                    result['status'] = constants.TASK_FATAL_ERROR
                result['statusmsg'] = ''
                result['errors'] = ''

            if isinstance(result['errors'], list):
                result['errors'] = "\n".join(result['errors'])
        elif result['status'] == constants.TASK_FATAL_ERROR:
            # NOTE: elif here is not the same as the following if
            result['errors'] = result['statusmsg']
            result['statusmsg'] = ''

        if result['status'] == constants.TASK_FATAL_ERROR:
            if raa.web.inWizardMode():
                self.wizardDone()
                result.update(reload=True)

        return result

    @raa.web.expose(allow_xmlrpc=True)
    @raa.web.require(raa.authorization.LocalhostOnly())
    def setWizardDone(self):
        log.info('setting wizard done')
        try:
            self._wizardDone()
        except Exception, e:
            log.error('error setting wizard done')
        return dict(message='successfully set wizard done')

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    @raa.web.require(raa.authorization.LocalhostOnly())
    def setNewEntitlement(self, key):
        self.plugins['/configure/Entitlements'].doSaveKey(key)
        return True

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def finalize(self):
        # mark finalized
        self.setPropertyValue('FINALIZED', True, RDT_BOOL)
        self.deletePropertyValue('FTS_SCHEDID')

        # mark done in the wizard
        self.wizardDone()

        # redirect to the rbuilder login screen
        fqdn = raa.web.getRequestHostname()
        query = { 'username': self.getPropertyValue('RBA_ADMIN', 'admin'),
                  'msg': "Please sign in below to enable platforms and complete the setup process." }
        raa.web.raiseHttpRedirect("http://%s/ui/#/login?%s" % (fqdn, urllib.urlencode(query)))
<|endoftext|>


<commit_before>#
# Copyright (c) 2006-2009 rPath, Inc
# All rights reserved
#
import time
import os
import md5
import simplejson
import re
import socket

from gettext import gettext as _
import raa
import raa.web
import logging
log = logging.getLogger('raa.web')

from raa.web import makeUrl, getCurrentUser, getRequestHeaderValue
from raa import rpath_error
from raa import constants
from raa.db.data import RDT_BOOL, RDT_INT, RDT_JSON, RDT_STRING
from raa.db import schedule
from raa.db import wizardrun
from raa.modules import raawebplugin
from raa.modules.raawebplugin import rAAWebPlugin
from raa.lib import validate

from mint import config
from mint import helperfuncs

from rPath.rbasetup import lib

ipAddrMatch = re.compile('^[0-9.]+$')
userNameMatch = re.compile('^[A-Za-z0-9._-]+$')
namespaceMatch = re.compile('^[A-Za-z0-9-]+$')

def validateNamespace(n):
    if not namespaceMatch.match(n):
        return "The namespace may only contain alphanumeric characters and hyphens."
    return ""

class rBASetup(rAAWebPlugin):
    """
    Represents the web side of the plugin.
    """
    # Name to be displayed in the side bar.
    displayName = _("rBuilder Setup")

    # Name to be displayed on mouse over in the side bar.
    tooltip = _("Plugin for setting up the rBuilder Appliance")

    wizardSerial = 1

    def initPlugin(self):
        self.checkSkipWizard()

    def checkSkipWizard(self):
        # If the system is already configured and this plug-in hasn't been run
        # in the wizard yet (i.e. migration) mark it as done.  We only want to
        # run in the wizard during install.
        isConfigured, cfg = lib.getRBAConfiguration()
        if isConfigured:
            # this initializes the pluginId dict which we need
            raa.modules.helper.getWebPluginIdDict()
            wizardRun = raa.web.getWebRoot().wizardRun

            # has wizard already run?
            if wizardRun.getLastRunTime(self.taskId, self.wizardSerial) is not None:
                return

            wizardRun.resetSession([(self.taskId, self.wizardSerial)])
            self._wizardDone()
            wizardRun.commit()

    def _getFirstTimeSetupStatus(self):
        # Get some status here
        schedId = self.getPropertyValue('FTS_SCHEDID', -1)
        if schedId == -1:
            return dict(schedId=-1, errors=['No setup task is running.'])
        status = raa.web.getWebRoot().callGetStatus(schedId)
        ret = dict(schedId=status['schedId'],
                   status=status['status'],
                   statusmsg=status['statusmsg'])

        return ret

    def _normalizeFormKwargs(self, kwargsFromForm):
        """
        Takes a pile of kwargs from the form post and normalizes it,
        stripping leading and trailing whitespace and converting
        booleans into the Boolean type.
        """
        boolean_options = ('requireSigs', 'configured', 'allowNamespaceChange')
        normalizedOptions = dict()

        for k, v in kwargsFromForm.iteritems():
            newV = v.strip()
            if k in boolean_options:
                normalizedOptions[k] = bool(int(str(v)))
            else:
                if newV:
                    normalizedOptions[k] = newV

        return normalizedOptions

    def _validateSetupForm(self, options):
        """
        Takes a list of options and validates them.
        Returns a list of errors (empty list if OK).
        """
        errorList = list()

        if not options['configured']:
            # validate the hostname
            if 'hostName' not in options:
                errorList.append("You must specify a fully-qualified hostname")
            elif ipAddrMatch.match(options['hostName']):
                errorList.append("The hostname appears to be an IP address.")
            elif not validate.validateFqdn(options['hostName']):
                errorList.append("The hostname is invalid.")

            # validate the site domain name
            if 'projectDomainName' not in options:
                errorList.append("You must specify a domain name for this installation.")
            elif ipAddrMatch.match(options['projectDomainName']):
                errorList.append("The domain name appears to be an IP address.")
            elif not validate.validateFqdn(options['projectDomainName']):
                errorList.append("The domain name is invalid.")

            # validate the username
            if 'new_username' not in options:
                errorList.append("You must enter a username to be created")
            elif not userNameMatch.match(options['new_username']):
                errorList.append("Your username must contain only alphnumeric characters, periods, hyphens, and underscores")

            # validate the email address
            if 'new_email' not in options:
                errorList.append("You must enter an administrator email address")

            # validate password
            if  ('new_password' not in options or 'new_password2' not in options):
                errorList.append("You must enter initial passwords")
            elif options['new_password'] != options['new_password2']:
                errorList.append("Passwords must match")
            elif options['new_password'].find('#') != -1:
                errorList.append("Passwords must not contain a #")

        if options.get('externalPasswordURL'):
            # XXX Removing the validation for this for now;
            #     it was more necessary when you could lock yourself
            #     out of rBuilder if you messed it up.
            #userAuth = netauth.UserAuthorization( \
            #    None, pwCheckUrl = options['externalPasswordURL'],
            #    cacheTimeout = int(options.get('authCacheTimeout')))
            #if self.auth.admin and not userAuth._checkPassword( \
            #    self.auth.username, None, None, self.auth.token[1]):
            #    errorList.append('Username: %s was not accepted by: %s' % \
            #                  (self.auth.username,
            #                   options['externalPasswordURL']))
            #if options.get('new_username') and \
            #       not userAuth._checkPassword(options.get('new_username'),
            #                                   None, None,
            #                                   options.get('new_password')):
            #    errorList.append('Username: %s was not accepted by: %s' % \
            #                  (options.get('new_username'),
            #                   options.get('new_password')))
            pass

        # validate the namespace
        if options.get('allowNamespaceChange'):
            namespaceValid = False
            if 'namespace' not in options:
                errorList.append("You must specify a namespace for this installation.")
            else:
                # returns text explanation if invalid
                errorString = validateNamespace(options['namespace'])
                if errorString:
                    errorList.append(errorString)

        return errorList

    @raa.web.expose(template="rPath.rbasetup.index")
    def index(self):
        # check to see if we're mid-setup

        status = self._getFirstTimeSetupStatus()
        if status['schedId'] != -1:
            raa.web.raiseHttpRedirect('/rbasetup/rBASetup/firstTimeSetup')

        # Get the configuration from the backend
        isConfigured, configurableOptions = lib.getRBAConfiguration()

        # if the namespace has already been set, don't allow them to change it
        # FIXME this needs to be changed - implemented for RBL-2905.
        dflNamespace = config.MintConfig().namespace
        allowNamespaceChange = not isConfigured or (configurableOptions['namespace'] == dflNamespace)

        # Backend returns Unicode for keys, which is suboptimal for Kid
        sanitizedConfigurableOptions = \
                dict([(str(k),v) for k, v in configurableOptions.iteritems()])

        # Return the hostname from the request if it's not set
        if not sanitizedConfigurableOptions['hostName'] or \
           ipAddrMatch.match(sanitizedConfigurableOptions['hostName']):
            fqdn = raa.web.getRequestHostname()
            ipAddr = None
            if ipAddrMatch.match(fqdn):
                # user is using IP address to access the rbuilder
                # suggest a hostname instead
                ipAddr = fqdn
                fqdn = os.uname()[1]
            if fqdn.find('.') == -1:
                # Maybe we're better off doing a reverse lookup
                try:
                    if not ipAddr:
                        ipAddr = socket.gethostbyname(fqdn)
                    fqdn = socket.gethostbyaddr(ipAddr)[0]
                except:
                    # nothing is fully-qualified.  :-(
                    pass
            bits = fqdn.split('.')
            sanitizedConfigurableOptions['hostName'] = '.'.join(bits)
            try:
                sanitizedConfigurableOptions['siteDomainName'] = '.'.join(bits[1:])
            except IndexError:
                sanitizedConfigurableOptions['siteDomainName'] = ''
            sanitizedConfigurableOptions['projectDomainName'] = sanitizedConfigurableOptions['siteDomainName']
            if not isConfigured and len(bits) > 1:
                sanitizedConfigurableOptions['namespace'] = bits[-2]

        return dict(isConfigured=isConfigured,
                allowNamespaceChange=allowNamespaceChange,
                **sanitizedConfigurableOptions)

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def saveConfig(self, *args, **kwargs):
        """
        Called upon posting the setup form.
        """
        # Normalize kwargs into a minimal set of options
        normalizedOptions = self._normalizeFormKwargs(kwargs)
        isConfigured, cfg = lib.getRBAConfiguration()

        # Validate the setup form
        errorList = self._validateSetupForm(normalizedOptions)
        if errorList:
            return dict(errors=errorList)

        # split hostname & siteDomainName
        if normalizedOptions.has_key('hostName'):
            hostname = normalizedOptions['hostName'].split('.')
            normalizedOptions['hostName'] = hostname[0]
            normalizedOptions['siteDomainName'] = '.'.join(hostname[1:])

        # Call backend to save generated file
        try:
            result = self.callBackend('updateRBAConfig', normalizedOptions)
            if result.has_key('errors'):
                return dict(errors=result['errors'])
            else:
                ret = dict(message="rBuilder Configuration saved.")
        except Exception, e:
            return dict(errors=["Failed to save rBuilder Configuration: %s" % repr(e)])

        # If this is a first time setup, we must kick off a redirect to do
        # post setup processing.
        if not isConfigured:
            username = raa.identity.current.user_name
            user = raa.identity.current_provider.authenticated_identity(username)
            raa.identity.current_provider.set_password(user,
                    normalizedOptions['new_password'])
            try:
                # do a simple check to catch the case where the user
                # did not fully qualify their hostname
                currentHostname = os.uname()[1]
                if currentHostname != '.'.join(hostname) and currentHostname == hostname[0]:
                    netCfg = self.plugins['/configure/Network'].index()
                    gatewayDHCP = False
                    if netCfg['host_gateway'] == '':
                        gatewayDHCP = True
                    log.info('saving new hostname')
                    self.plugins['/configure/Network']._saveGeneral(
                         netCfg['host_usesdhcp'],
                         '.'.join(hostname),
                         netCfg['dns_dnsDomain'],
                         netCfg['dns_dnsServer'],
                         gatewayDHCP,
                         netCfg['host_gateway'],
                         netCfg['host_gatewaydev'],
                         netCfg['dns_dnsdhcp'],
                         )
                    log.info('new hostname saved')
            except Exception, e:
                log.error('Hostname reset failed.  Continuing anyway...')
            # Kick off the first time processing
            sched = schedule.ScheduleOnce(time.time() + 1)
            schedId = self.callBackendAsync(sched, 'firstTimeSetup', normalizedOptions)
            self.setPropertyValue('FTS_SCHEDID', schedId, RDT_INT)
            # raa.web.raiseHttpRedirect('/rbasetup/rBASetup/firstTimeSetup')
            return True
        else:
            return ret

        return dict(errors=['An unexpected condition occurred'])

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def callGetStatus(self, schedId):
        return raa.web.getWebRoot().callGetStatus(schedId)

    @raa.web.expose(allow_json=True)
    def retryFirstTimeSetup(self):
        currentStatus = self._getFirstTimeSetupStatus()
        if currentStatus['status'] == constants.TASK_FATAL_ERROR or \
            (currentStatus['status'] == constants.TASK_SUCCESS and
             not self.getPropertyValue('FINALIZED', False)):
            sched = schedule.ScheduleOnce(time.time() + 1)
            schedId = self.callBackendAsync(sched, 'firstTimeSetup', dict(retry=True))
            self.deletePropertyValue('FTS_SCHEDID')
            self.setPropertyValue('FTS_SCHEDID', schedId, RDT_INT)
            return self._getFirstTimeSetupStatus()
        else:
            log.error('not retrying FTS: status is %s' % currentStatus['status'] )
        return

    @raa.web.expose(allow_xmlrpc=True, allow_json=True, template="rPath.rbasetup.firstTimeSetup")
    def firstTimeSetup(self):
        status = self._getFirstTimeSetupStatus()
        if status['schedId'] == -1:
            raa.web.raiseHttpRedirect('/rbasetup/rBASetup/')
        return status

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def getFirstTimeSetupStatus(self):
        result = self._getFirstTimeSetupStatus()
        result.update(reload=False)
        if result['schedId'] == -1:
            return result

        # if the task is complete, get the return values
        if result['status'] == constants.TASK_SUCCESS:
            try:
                isException, ret = raa.web.getWebRoot().simpleTasks.getResult(result['schedId'])
                if isException:
                    try:
                        result['errors'] = " ".join( [ x.encode('utf-8') for x in ret ])
                    except:
                        result['errors'] = " ".join( [ x for x in ret ] )
                    result['status'] = constants.TASK_FATAL_ERROR
                    result['statusmsg'] = ''
                else:
                    result['statusmsg'] = ret.get('message', '')
                    result['errors'] = ret.get('errors', '')
                    if result['errors'] != '':
                        result['status'] = constants.TASK_FATAL_ERROR
            except Exception, e:
                # The task no longer exists
                finalized = self.getPropertyValue('FINALIZED', False)
                if not finalized:
                    # assume that it failed.  rerunning should not hurt.
                    result['status'] = constants.TASK_FATAL_ERROR
                result['statusmsg'] = ''
                result['errors'] = ''

            if isinstance(result['errors'], list):
                result['errors'] = "\n".join(result['errors'])
        elif result['status'] == constants.TASK_FATAL_ERROR:
            # NOTE: elif here is not the same as the following if
            result['errors'] = result['statusmsg']
            result['statusmsg'] = ''

        if result['status'] == constants.TASK_FATAL_ERROR:
            if raa.web.inWizardMode():
                self.wizardDone()
                result.update(reload=True)

        return result

    @raa.web.expose(allow_xmlrpc=True)
    @raa.web.require(raa.authorization.LocalhostOnly())
    def setWizardDone(self):
        log.info('setting wizard done')
        try:
            self._wizardDone()
        except Exception, e:
            log.error('error setting wizard done')
        return dict(message='successfully set wizard done')

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    @raa.web.require(raa.authorization.LocalhostOnly())
    def setNewEntitlement(self, key):
        self.plugins['/configure/Entitlements'].doSaveKey(key)
        return True

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def finalize(self):
        # mark finalized
        self.setPropertyValue('FINALIZED', True, RDT_BOOL)
        self.deletePropertyValue('FTS_SCHEDID')

        # mark done in the wizard
        self.wizardDone()

        # redirect to the rbuilder itself
        isConfigured, configurableOptions = lib.getRBAConfiguration()
        hostname = configurableOptions['hostName']
        dn = configurableOptions['siteDomainName']
        raa.web.raiseHttpRedirect("http://%s.%s/" % (hostname, dn))
<commit_msg>Change post-setup redirect to use the browser URL instead of the configured one. (RBL-5138)<commit_after>#
# Copyright (c) 2006-2009 rPath, Inc
# All rights reserved
#
import time
import os
import md5
import simplejson
import re
import socket

from gettext import gettext as _
import raa
import raa.web
import logging
log = logging.getLogger('raa.web')

from raa import rpath_error
from raa import constants
from raa.db.data import RDT_BOOL, RDT_INT, RDT_JSON, RDT_STRING
from raa.db import schedule
from raa.db import wizardrun
from raa.modules import raawebplugin
from raa.modules.raawebplugin import rAAWebPlugin
from raa.lib import validate

from mint import config
from mint import helperfuncs

from rPath.rbasetup import lib

ipAddrMatch = re.compile('^[0-9.]+$')
userNameMatch = re.compile('^[A-Za-z0-9._-]+$')
namespaceMatch = re.compile('^[A-Za-z0-9-]+$')

def validateNamespace(n):
    if not namespaceMatch.match(n):
        return "The namespace may only contain alphanumeric characters and hyphens."
    return ""

class rBASetup(rAAWebPlugin):
    """
    Represents the web side of the plugin.
    """
    # Name to be displayed in the side bar.
    displayName = _("rBuilder Setup")

    # Name to be displayed on mouse over in the side bar.
    tooltip = _("Plugin for setting up the rBuilder Appliance")

    wizardSerial = 1

    def initPlugin(self):
        self.checkSkipWizard()

    def checkSkipWizard(self):
        # If the system is already configured and this plug-in hasn't been run
        # in the wizard yet (i.e. migration) mark it as done.  We only want to
        # run in the wizard during install.
        isConfigured, cfg = lib.getRBAConfiguration()
        if isConfigured:
            # this initializes the pluginId dict which we need
            raa.modules.helper.getWebPluginIdDict()
            wizardRun = raa.web.getWebRoot().wizardRun

            # has wizard already run?
            if wizardRun.getLastRunTime(self.taskId, self.wizardSerial) is not None:
                return

            wizardRun.resetSession([(self.taskId, self.wizardSerial)])
            self._wizardDone()
            wizardRun.commit()

    def _getFirstTimeSetupStatus(self):
        # Get some status here
        schedId = self.getPropertyValue('FTS_SCHEDID', -1)
        if schedId == -1:
            return dict(schedId=-1, errors=['No setup task is running.'])
        status = raa.web.getWebRoot().callGetStatus(schedId)
        ret = dict(schedId=status['schedId'],
                   status=status['status'],
                   statusmsg=status['statusmsg'])

        return ret

    def _normalizeFormKwargs(self, kwargsFromForm):
        """
        Takes a pile of kwargs from the form post and normalizes it,
        stripping leading and trailing whitespace and converting
        booleans into the Boolean type.
        """
        boolean_options = ('requireSigs', 'configured', 'allowNamespaceChange')
        normalizedOptions = dict()

        for k, v in kwargsFromForm.iteritems():
            newV = v.strip()
            if k in boolean_options:
                normalizedOptions[k] = bool(int(str(v)))
            else:
                if newV:
                    normalizedOptions[k] = newV

        return normalizedOptions

    def _validateSetupForm(self, options):
        """
        Takes a list of options and validates them.
        Returns a list of errors (empty list if OK).
        """
        errorList = list()

        if not options['configured']:
            # validate the hostname
            if 'hostName' not in options:
                errorList.append("You must specify a fully-qualified hostname")
            elif ipAddrMatch.match(options['hostName']):
                errorList.append("The hostname appears to be an IP address.")
            elif not validate.validateFqdn(options['hostName']):
                errorList.append("The hostname is invalid.")

            # validate the site domain name
            if 'projectDomainName' not in options:
                errorList.append("You must specify a domain name for this installation.")
            elif ipAddrMatch.match(options['projectDomainName']):
                errorList.append("The domain name appears to be an IP address.")
            elif not validate.validateFqdn(options['projectDomainName']):
                errorList.append("The domain name is invalid.")

            # validate the username
            if 'new_username' not in options:
                errorList.append("You must enter a username to be created")
            elif not userNameMatch.match(options['new_username']):
                errorList.append("Your username must contain only alphnumeric characters, periods, hyphens, and underscores")

            # validate the email address
            if 'new_email' not in options:
                errorList.append("You must enter an administrator email address")

            # validate password
            if  ('new_password' not in options or 'new_password2' not in options):
                errorList.append("You must enter initial passwords")
            elif options['new_password'] != options['new_password2']:
                errorList.append("Passwords must match")
            elif options['new_password'].find('#') != -1:
                errorList.append("Passwords must not contain a #")

        if options.get('externalPasswordURL'):
            # XXX Removing the validation for this for now;
            #     it was more necessary when you could lock yourself
            #     out of rBuilder if you messed it up.
            #userAuth = netauth.UserAuthorization( \
            #    None, pwCheckUrl = options['externalPasswordURL'],
            #    cacheTimeout = int(options.get('authCacheTimeout')))
            #if self.auth.admin and not userAuth._checkPassword( \
            #    self.auth.username, None, None, self.auth.token[1]):
            #    errorList.append('Username: %s was not accepted by: %s' % \
            #                  (self.auth.username,
            #                   options['externalPasswordURL']))
            #if options.get('new_username') and \
            #       not userAuth._checkPassword(options.get('new_username'),
            #                                   None, None,
            #                                   options.get('new_password')):
            #    errorList.append('Username: %s was not accepted by: %s' % \
            #                  (options.get('new_username'),
            #                   options.get('new_password')))
            pass

        # validate the namespace
        if options.get('allowNamespaceChange'):
            namespaceValid = False
            if 'namespace' not in options:
                errorList.append("You must specify a namespace for this installation.")
            else:
                # returns text explanation if invalid
                errorString = validateNamespace(options['namespace'])
                if errorString:
                    errorList.append(errorString)

        return errorList

    @raa.web.expose(template="rPath.rbasetup.index")
    def index(self):
        # check to see if we're mid-setup

        status = self._getFirstTimeSetupStatus()
        if status['schedId'] != -1:
            raa.web.raiseHttpRedirect('/rbasetup/rBASetup/firstTimeSetup')

        # Get the configuration from the backend
        isConfigured, configurableOptions = lib.getRBAConfiguration()

        # if the namespace has already been set, don't allow them to change it
        # FIXME this needs to be changed - implemented for RBL-2905.
        dflNamespace = config.MintConfig().namespace
        allowNamespaceChange = not isConfigured or (configurableOptions['namespace'] == dflNamespace)

        # Backend returns Unicode for keys, which is suboptimal for Kid
        sanitizedConfigurableOptions = \
                dict([(str(k),v) for k, v in configurableOptions.iteritems()])

        # Return the hostname from the request if it's not set
        if not sanitizedConfigurableOptions['hostName'] or \
           ipAddrMatch.match(sanitizedConfigurableOptions['hostName']):
            fqdn = raa.web.getRequestHostname()
            ipAddr = None
            if ipAddrMatch.match(fqdn):
                # user is using IP address to access the rbuilder
                # suggest a hostname instead
                ipAddr = fqdn
                fqdn = os.uname()[1]
            if fqdn.find('.') == -1:
                # Maybe we're better off doing a reverse lookup
                try:
                    if not ipAddr:
                        ipAddr = socket.gethostbyname(fqdn)
                    fqdn = socket.gethostbyaddr(ipAddr)[0]
                except:
                    # nothing is fully-qualified.  :-(
                    pass
            bits = fqdn.split('.')
            sanitizedConfigurableOptions['hostName'] = '.'.join(bits)
            try:
                sanitizedConfigurableOptions['siteDomainName'] = '.'.join(bits[1:])
            except IndexError:
                sanitizedConfigurableOptions['siteDomainName'] = ''
            sanitizedConfigurableOptions['projectDomainName'] = sanitizedConfigurableOptions['siteDomainName']
            if not isConfigured and len(bits) > 1:
                sanitizedConfigurableOptions['namespace'] = bits[-2]

        return dict(isConfigured=isConfigured,
                allowNamespaceChange=allowNamespaceChange,
                **sanitizedConfigurableOptions)

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def saveConfig(self, *args, **kwargs):
        """
        Called upon posting the setup form.
        """
        # Normalize kwargs into a minimal set of options
        normalizedOptions = self._normalizeFormKwargs(kwargs)
        isConfigured, cfg = lib.getRBAConfiguration()

        # Validate the setup form
        errorList = self._validateSetupForm(normalizedOptions)
        if errorList:
            return dict(errors=errorList)

        # split hostname & siteDomainName
        if normalizedOptions.has_key('hostName'):
            hostname = normalizedOptions['hostName'].split('.')
            normalizedOptions['hostName'] = hostname[0]
            normalizedOptions['siteDomainName'] = '.'.join(hostname[1:])

        # Call backend to save generated file
        try:
            result = self.callBackend('updateRBAConfig', normalizedOptions)
            if result.has_key('errors'):
                return dict(errors=result['errors'])
            else:
                ret = dict(message="rBuilder Configuration saved.")
        except Exception, e:
            return dict(errors=["Failed to save rBuilder Configuration: %s" % repr(e)])

        # If this is a first time setup, we must kick off a redirect to do
        # post setup processing.
        if not isConfigured:
            username = raa.identity.current.user_name
            user = raa.identity.current_provider.authenticated_identity(username)
            raa.identity.current_provider.set_password(user,
                    normalizedOptions['new_password'])
            try:
                # do a simple check to catch the case where the user
                # did not fully qualify their hostname
                currentHostname = os.uname()[1]
                if currentHostname != '.'.join(hostname) and currentHostname == hostname[0]:
                    netCfg = self.plugins['/configure/Network'].index()
                    gatewayDHCP = False
                    if netCfg['host_gateway'] == '':
                        gatewayDHCP = True
                    log.info('saving new hostname')
                    self.plugins['/configure/Network']._saveGeneral(
                         netCfg['host_usesdhcp'],
                         '.'.join(hostname),
                         netCfg['dns_dnsDomain'],
                         netCfg['dns_dnsServer'],
                         gatewayDHCP,
                         netCfg['host_gateway'],
                         netCfg['host_gatewaydev'],
                         netCfg['dns_dnsdhcp'],
                         )
                    log.info('new hostname saved')
            except Exception, e:
                log.error('Hostname reset failed.  Continuing anyway...')
            # Kick off the first time processing
            sched = schedule.ScheduleOnce(time.time() + 1)
            schedId = self.callBackendAsync(sched, 'firstTimeSetup', normalizedOptions)
            self.setPropertyValue('FTS_SCHEDID', schedId, RDT_INT)
            # raa.web.raiseHttpRedirect('/rbasetup/rBASetup/firstTimeSetup')
            return True
        else:
            return ret

        return dict(errors=['An unexpected condition occurred'])

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def callGetStatus(self, schedId):
        return raa.web.getWebRoot().callGetStatus(schedId)

    @raa.web.expose(allow_json=True)
    def retryFirstTimeSetup(self):
        currentStatus = self._getFirstTimeSetupStatus()
        if currentStatus['status'] == constants.TASK_FATAL_ERROR or \
            (currentStatus['status'] == constants.TASK_SUCCESS and
             not self.getPropertyValue('FINALIZED', False)):
            sched = schedule.ScheduleOnce(time.time() + 1)
            schedId = self.callBackendAsync(sched, 'firstTimeSetup', dict(retry=True))
            self.deletePropertyValue('FTS_SCHEDID')
            self.setPropertyValue('FTS_SCHEDID', schedId, RDT_INT)
            return self._getFirstTimeSetupStatus()
        else:
            log.error('not retrying FTS: status is %s' % currentStatus['status'] )
        return

    @raa.web.expose(allow_xmlrpc=True, allow_json=True, template="rPath.rbasetup.firstTimeSetup")
    def firstTimeSetup(self):
        status = self._getFirstTimeSetupStatus()
        if status['schedId'] == -1:
            raa.web.raiseHttpRedirect('/rbasetup/rBASetup/')
        return status

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def getFirstTimeSetupStatus(self):
        result = self._getFirstTimeSetupStatus()
        result.update(reload=False)
        if result['schedId'] == -1:
            return result

        # if the task is complete, get the return values
        if result['status'] == constants.TASK_SUCCESS:
            try:
                isException, ret = raa.web.getWebRoot().simpleTasks.getResult(result['schedId'])
                if isException:
                    try:
                        result['errors'] = " ".join( [ x.encode('utf-8') for x in ret ])
                    except:
                        result['errors'] = " ".join( [ x for x in ret ] )
                    result['status'] = constants.TASK_FATAL_ERROR
                    result['statusmsg'] = ''
                else:
                    result['statusmsg'] = ret.get('message', '')
                    result['errors'] = ret.get('errors', '')
                    if result['errors'] != '':
                        result['status'] = constants.TASK_FATAL_ERROR
            except Exception, e:
                # The task no longer exists
                finalized = self.getPropertyValue('FINALIZED', False)
                if not finalized:
                    # assume that it failed.  rerunning should not hurt.
                    result['status'] = constants.TASK_FATAL_ERROR
                result['statusmsg'] = ''
                result['errors'] = ''

            if isinstance(result['errors'], list):
                result['errors'] = "\n".join(result['errors'])
        elif result['status'] == constants.TASK_FATAL_ERROR:
            # NOTE: elif here is not the same as the following if
            result['errors'] = result['statusmsg']
            result['statusmsg'] = ''

        if result['status'] == constants.TASK_FATAL_ERROR:
            if raa.web.inWizardMode():
                self.wizardDone()
                result.update(reload=True)

        return result

    @raa.web.expose(allow_xmlrpc=True)
    @raa.web.require(raa.authorization.LocalhostOnly())
    def setWizardDone(self):
        log.info('setting wizard done')
        try:
            self._wizardDone()
        except Exception, e:
            log.error('error setting wizard done')
        return dict(message='successfully set wizard done')

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    @raa.web.require(raa.authorization.LocalhostOnly())
    def setNewEntitlement(self, key):
        self.plugins['/configure/Entitlements'].doSaveKey(key)
        return True

    @raa.web.expose(allow_xmlrpc=True, allow_json=True)
    def finalize(self):
        # mark finalized
        self.setPropertyValue('FINALIZED', True, RDT_BOOL)
        self.deletePropertyValue('FTS_SCHEDID')

        # mark done in the wizard
        self.wizardDone()

        # redirect to the rbuilder itself
        fqdn = raa.web.getRequestHostname()
        raa.web.raiseHttpRedirect("http://%s/" % (fqdn,))
<|endoftext|>