Example #1
0
def add_pubkey():
    if cydra_user.is_guest:
        raise InsufficientPermissions()

    try:
        from twisted.conch.ssh import keys
        import base64
    except:
        return redirect(url_for('.usersettings'))

    pubkey = request.form.get('key_data', None)
    name = request.form.get('key_name', None)

    if pubkey is None or name is None:
        raise BadRequest('Key or Name missing')

    try:
        pubkey = keys.Key.fromString(pubkey)
    except:
        logger.exception("Unable to parse key")
        flash('Unable to parse key', 'error')
    else:
        store = ExtensionPoint(IPubkeyStore, component_manager=cydra_instance)
        store.add_pubkey(cydra_user, pubkey.blob(), name, pubkey.fingerprint())

    return redirect(url_for('.usersettings'))
Example #2
0
def project(projectname):
    project = cydra_instance.get_project(projectname)
    if project is None:
        abort(404)

    if not project.get_permission(cydra_user, '*', 'read'):
        raise InsufficientPermissions()

    repo_viewer_providers = ExtensionPoint(IRepositoryViewerProvider,
                                           component_manager=cydra_instance)
    project_action_providers = ExtensionPoint(IProjectActionProvider,
                                              component_manager=cydra_instance)
    repository_action_providers = ExtensionPoint(
        IRepositoryActionProvider, component_manager=cydra_instance)
    featurelist_item_providers = ExtensionPoint(
        IProjectFeaturelistItemProvider, component_manager=cydra_instance)

    return render_template(
        'project.jhtml',
        project=project,
        get_viewers=get_collator(repo_viewer_providers.get_repository_viewers),
        project_actions=get_collator(
            project_action_providers.get_project_actions)(project),
        get_repository_actions=get_collator(
            repository_action_providers.get_repository_actions),
        featurelist=get_collator(
            featurelist_item_providers.get_project_featurelist_items)(project))
Example #3
0
def remove_pubkey():
    if cydra_user.is_guest:
        raise InsufficientPermissions()

    fingerprint = request.form.get('fingerprint', None)
    if fingerprint is None:
        raise BadRequest('Fingerprint missing')

    store = ExtensionPoint(IPubkeyStore, component_manager=cydra_instance)
    store.remove_pubkey(cydra_user, fingerprint=fingerprint)

    return redirect(url_for('.usersettings'))
Example #4
0
def usersettings():
    if cydra_user.is_guest:
        raise InsufficientPermissions()

    pubkey_support = True
    pubkeys = []
    try:
        from twisted.conch.ssh import keys
    except:
        pubkey_support = False
    else:
        store = ExtensionPoint(IPubkeyStore, component_manager=cydra_instance)
        pubkey_support = len(store) > 0
        pubkeys = store.get_pubkeys(cydra_user)

    return render_template('usersettings.jhtml', pubkey_support=pubkey_support, pubkeys=pubkeys)
Example #5
0
File: git.py Project: mensi/cydra
class GitRepository(Repository):

    permission = ExtensionPoint(IPermissionProvider)

    def __init__(self, component_manager, base, project, name):
        """Construct an instance"""
        super(GitRepository, self).__init__(component_manager)

        self.project = project
        self.name = name
        self.base = base
        self.type = 'git'

        self.path = self.path = os.path.abspath(os.path.join(self.base, project.name, name + '.git'))

        # ensure this repository actually exists
        if not os.path.exists(self.path):
            raise UnknownRepository(repository_name=name, project_name=project.name, repository_type='git')

    def get_param(self, param):
        if param == 'description':
            descrfile = os.path.join(self.path, 'description')

            if os.path.exists(descrfile):
                with open(descrfile, 'r') as f:
                    return f.read()

    def set_params(self, **params):
        if 'description' in params:
            descrfile = os.path.join(self.path, 'description')

            with open(descrfile, 'w') as f:
                f.write(params['description'])

    def sync(self):
        """Installs necessary hooks"""
        from jinja2 import Template

        tpl = self.compmgr.config.get_component_config('cydra.repository.git.GitRepositories', {}).get('post_receive_script')
        if tpl:
            with open(tpl, 'r') as f:
                template = Template(f.read())
        else:
            from pkg_resources import resource_string
            template = Template(resource_string('cydra.repository', 'scripts/git_post-receive.sh'))

        hook = template.render(project=self.project, repository=self)
        with open(os.path.join(self.path, 'hooks', 'post-receive'), 'w') as f:
            f.write(hook)
            mode = os.fstat(f.fileno()).st_mode
            mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
            os.fchmod(f.fileno(), mode)

        super(GitRepository, self).sync()

    def has_read_access(self, user):
        return self.project.get_permission(user, 'repository.git.' + self.name, 'read')

    def has_write_access(self, user):
        return self.project.get_permission(user, 'repository.git.' + self.name, 'write')
Example #6
0
class WSGIAuthnzHelper(object):

    translator = ExtensionPoint(IUserTranslator)
    authenticator = ExtensionPoint(IUserAuthenticator)

    def __init__(self, environ_to_perm, cyd=None):
        """Initialize Authnz helper
        
        :param environ_to_perm: Callable that returns the project or project name and the object an environment corresponds to as a tuple(project, object)"""

        if cyd is None:
            cyd = cydra.Cydra()

        self.cydra = self.compmgr = cyd
        self.environ_to_perm = environ_to_perm

    def check_password(self, environ, username, password):
        """Function for WSGIAuthUserScript
        
        Authenticates the user regardless of environment"""
        user = self.translator.username_to_user(username)

        return self.authenticator.user_password(user, password)

    def groups_for_user(self, environ, username):
        """Function for WSGIAuthGroupScript
        
        Returns the permissions a user has on the object corresponding to the environment"""
        user = self.translator.username_to_user(username)

        project, obj = self.environ_to_perm(environ)

        if isinstance(project, basestring):
            project = self.cydra.get_project(project)

            if project is None:
                return []

        return [
            perm.encode('utf-8')
            for (perm, value) in project.get_permissions(user, obj).items()
            if value == True
        ]
Example #7
0
class SVNRepository(Repository):

    permission = ExtensionPoint(IPermissionProvider)

    def __init__(self, component_manager, base, project):
        """Construct repository instance"""
        super(SVNRepository, self).__init__(component_manager)

        self.project = project
        self.name = project.name
        self.base = base
        self.type = 'svn'

        self.path = self.path = os.path.abspath(
            os.path.join(self.base, self.name))

        # ensure this repository actually exists
        if not os.path.exists(self.path):
            raise UnknownRepository(repository_name=self.name,
                                    project_name=project.name,
                                    repository_type='svn')

    def has_read_access(self, user):
        return self.project.get_permission(user, 'repository.svn.' + self.name,
                                           'read')

    def has_write_access(self, user):
        return self.project.get_permission(user, 'repository.svn.' + self.name,
                                           'write')

    def sync(self):
        """Installs necessary hooks"""
        from jinja2 import Template

        tpl = self.compmgr.config.get_component_config(
            'cydra.repository.svn.SVNRepositories', {}).get('commit_script')
        if tpl:
            with open(tpl, 'r') as f:
                template = Template(f.read())
        else:
            from pkg_resources import resource_string
            template = Template(
                resource_string('cydra.repository', 'scripts/svn_commit.sh'))

        hook = template.render(project=self.project, repository=self)
        with open(os.path.join(self.path, 'hooks', 'post-commit'), 'w') as f:
            f.write(hook)
            mode = os.fstat(f.fileno()).st_mode
            mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
            os.fchmod(f.fileno(), mode)

        super(SVNRepository, self).sync()
Example #8
0
    def __init__(self, cydra_instance):
        super(ProjectCommand, self).__init__(cydra_instance)

        further_commands = ExtensionPoint(
            ICliProjectCommandProvider,
            component_manager=self.cydra).get_cli_project_commands()
        commands = []
        for x in further_commands:
            if x is None:
                continue
            commands.extend(x)

        for cmd in commands:
            func = lambda x, y: cmd[1](self.project, y)
            func.__doc__ = cmd[1].__doc__
            func.__name__ = cmd[0]
            setattr(self, cmd[0], types.MethodType(func, self, self.__class__))
Example #9
0
class HTTPBasicAuthenticator(object):

    translator = ExtensionPoint(IUserTranslator)
    authenticator = ExtensionPoint(IUserAuthenticator)

    def __init__(self, cyd=None):
        if cyd is None:
            cyd = cydra.Cydra()

        self.cydra = self.compmgr = cyd
        self.cache = SimpleCache()

    def __call__(self, environ):
        # default to guest
        environ['cydra_user'] = self.cydra.get_user(userid='*')

        # consult the REMOTE_USER variable. If this is set, apache or some other part
        # already did the authetication. We will trust that judgement
        userid = environ.get('REMOTE_USER', None)
        logger.debug('Remote user: "******"', str(userid))

        if userid is None:
            # Nothing already set. Perform HTTP auth ourselves
            author = environ.get('HTTP_AUTHORIZATION', None)
            logger.debug(
                "No remote user supplied, trying Authorization header")

            if author is None:
                logger.debug("No Authorization header supplied")
                return self.cydra.get_user(userid='*')

            if author.strip().lower()[:5] != 'basic':
                # atm only basic is supported
                logging.warning(
                    "User tried to use a different auth method (not basic): %s"
                )
                return self.cydra.get_user(userid='*')

            userpw_base64 = author.strip()[5:].strip()
            if userpw_base64 in self.cache:
                # login cached as successful, we can now set REMOTE_USER for further use
                user = self.cache.get(userpw_base64)
                logger.debug('Author header found in cache, user: %s (%s)',
                             user.full_name, user.userid)
                environ['REMOTE_USER'] = user.userid
                environ['cydra_user'] = user
                return user

            userid, pw = userpw_base64.decode('base64').split(':', 1)

            # yes, you probably don't want to leak information about a password
            # such as its length into the log. The length helps to debug issues with extra
            # whitespace though
            logger.debug('Got user "%s" with passwordlen %d. Agent: %s',
                         userid, len(pw), environ.get('HTTP_USER_AGENT',
                                                      'None'))

            if is_urldecode_necessary(environ.get('HTTP_USER_AGENT', '')):
                logger.info('Client is broken w.r.t. url encoding: %s',
                            environ.get('HTTP_USER_AGENT', 'None'))
                userid = urllib.unquote(userid)
                pw = urllib.unquote(pw)

            user = self.translator.username_to_user(userid)
            if user is None:
                logger.debug('Lookup for %s failed', userid)
                user = self.cydra.get_user(userid='*')

            logger.debug('User lookup gave %s (%s)', user.full_name,
                         user.userid)

            if user.is_guest:
                logger.info('User %s resolved to guest', userid)
                return user

            elif self.authenticator.user_password(user, pw):
                # login successful, we can now set REMOTE_USER for further use
                environ['REMOTE_USER'] = user.userid
                environ['cydra_user'] = user

                # and cache
                logger.debug('Caching login data for %s (%s)', user.full_name,
                             user.userid)
                self.cache.set(userpw_base64, user)

                return user
            else:
                logger.info('User %s (%s) supplied a wrong password',
                            user.full_name, user.userid)
                return self.cydra.get_user(userid='*')
        else:
            logger.debug("Got REMOTE_USER=%s", userid)

            if userid in self.cache:
                user = self.cache.get(userid)
                environ['cydra_user'] = user
            else:
                user = self.translator.username_to_user(userid)

                if user is None:
                    logger.debug('Lookup for %s failed', userid)
                    user = self.cydra.get_user(userid='*')

                if not user.is_guest:
                    self.cache.set(userid, user)
                    environ['cydra_user'] = user

            return user
Example #10
0
class Configuration(Component):
    """Encapsulates the configuration
    
    Configuration is held as a dict
    
    Example::
    
        {
            'plugin_paths': ['/some/path'],
            'components':
            {
                'component_a': True,
                'component_b': {'someoption': 42}
            },
            'extension_points':
            {
                'ep_a': {'someoption': 42}
            }
        }
    """

    configuration_providers = ExtensionPoint(IConfigurationProvider)

    def __init__(self):
        """Initialize Configuration"""
        self.cydra = self.compmgr
        self._data = {}

    def get(self, key, default=None):
        return self._data.get(key, default)

    def get_component_config(self, component, default=None):
        """Find configuration node for a component
        
        """
        ret = self._data.setdefault('components', {}).get(component, default)

        # since setting a component to True or False is a short-hand to enable/disable
        # the component, also return the default in this case
        if isinstance(ret, bool):
            return default
        else:
            return ret

    def is_component_enabled(self, component):
        return bool(
            self._data.setdefault('components', {}).get(component, False))

    def load(self, config=None):
        """Load configuration data into the config tree
        
        If config is None, the default configuration will be loaded
        """
        providers = set(self.configuration_providers
                        )  # copy the providers for later reference

        if config is not None:
            self._load(config)
        elif self._data == {} or self._data == {
                'components': {}
        }:  #only load default config if config is empty
            self._load(default_configuration)

        # if new configuration providers have been enabled, query them
        for provider in self.configuration_providers:
            if provider not in providers:
                self.load(provider.get_config())

    def _load(self, config):

        root = self._data
        plugin_paths_dirty = False

        for k, v in config.iteritems():
            if k == 'plugin_paths':
                self._data.setdefault('plugin_paths',
                                      set()).update(config['plugin_paths'])
                plugin_paths_dirty = True
            else:
                if isinstance(v, dict):
                    self.merge(self._data.setdefault(k, dict()), v)
                elif isinstance(v, list):
                    self._data.setdefault(k, list()).extend(v)
                elif isinstance(v, set):
                    self._data.setdefault(k, set()).update(v)
                else:
                    self._data[k] = v

        if plugin_paths_dirty:
            load_components(self.cydra, self._data.get(
                'plugin_paths', set()))  # new plugin paths, load from those

    def merge(self, dest, source):
        """Merges a subtree into a subtree of the config
        
        Recurses into dicts
        
        :param dest: Object to merge into. This should be a node of the config tree
        :param source: Source for merge.
        """
        if type(dest) != type(source):
            raise MergeException("Types do not match: %s, %s" %
                                 (type(dest).__name__, type(source).__name__))

        if isinstance(dest, dict):
            for k, v in source.iteritems():
                if isinstance(v, dict):
                    self.merge(dest.setdefault(k, dict()), v)
                elif isinstance(v, list):
                    dest.setdefault(k, list()).extend(v)
                elif isinstance(v, set):
                    dest.setdefault(k, set()).update(v)
                else:
                    dest[k] = v
        else:
            raise MergeException("Unhandled type: " + type(dest).__name__)
Example #11
0
class Cydra(Component, ComponentManager):
    """Main point of integration
    
    """
    datasource = ExtensionPoint(IDataSource)
    permission = ExtensionPoint(IPermissionProvider)
    translator = ExtensionPoint(IUserTranslator)
    subject_cache = ExtensionPoint(ISubjectCache)

    def __init__(self, config=None):
        logging.basicConfig(level=logging.DEBUG)

        ComponentManager.__init__(self)

        load_components(self)

        self.config = Configuration(self)
        self.config.load(config)

        logger.debug("Configuration loaded: %s", repr(self.config._data))

    def get_user(self, userid=None, username=None):
        """Convenience function for user retrieval"""
        if userid == '*':
            return User(self, '*', username='******', full_name='Guest')

        if userid is None and username is None:
            raise ValueError("Neither userid nor username given")

        result = None
        failed_caches = []

        # go to caches
        for cache in self.subject_cache:
            if userid is not None:
                result = cache.get_user(userid)
            elif username is not None:
                result = cache.get_user_by_name(username)

            if result is not None:
                break
            else:
                failed_caches.append(cache)

        # not found
        if result is None:
            if userid is not None:
                result = self.translator.userid_to_user(userid)
            elif username is not None:
                result = self.translator.username_to_user(username)

        if result is None:
            return result

        # update caches
        for cache in failed_caches:
            cache.add_users([result])

        return result

    def get_group(self, groupid):
        """Convenience function for group retrieval"""
        if groupid is None:
            raise ValueError("groupid mustn't be None")

        result = None
        failed_caches = []

        # go to caches
        for cache in self.subject_cache:
            result = cache.get_group(groupid)

            if result is not None:
                break
            else:
                failed_caches.append(cache)

        # not found
        if result is None:
            result = self.translator.groupid_to_group(groupid)

        if result is None:
            return result

        # update caches
        for cache in failed_caches:
            cache.add_groups([result])

        return result

    def get_project(self, name):
        return self.datasource.get_project(name)

    def get_projects(self, *args, **kwargs):
        return self.datasource.list_projects(*args, **kwargs)

    def get_project_names(self, *args, **kwargs):
        return self.datasource.get_project_names(*args, **kwargs)

    def get_projects_owned_by(self, *args, **kwargs):
        return self.datasource.get_projects_owned_by(*args, **kwargs)

    def get_projects_where_key_exists(self, *args, **kwargs):
        return self.datasource.get_projects_where_key_exists(*args, **kwargs)

    def get_projects_user_has_permissions_on(self, user):
        """Convenience function to retrieve all projects a user has permissions on"""
        return self.permission.get_projects_user_has_permissions_on(user)

    def get_permissions(self, user, object):
        """Convenience function for global permission enumeration"""
        return self.permission.get_permissions(None, user, object)

    def get_permission(self, user, object, permission):
        """Convenience function for permission retrieval"""
        return self.permission.get_permission(None, user, object, permission)

    def set_permission(self, user, object, permission, value):
        """Convenience function for permission retrieval"""
        return self.permission.set_permission(None, user, object, permission, value)

    def is_component_enabled(self, cls):
        component_name = cls.__module__ + '.' + cls.__name__

        return self.config.is_component_enabled(component_name)
Example #12
0
class Configuration(Component):
    """Encapsulates the configuration

    Configuration is held as a dict

    Example::

        {
            'plugin_paths': ['/some/path'],
            'components':
            {
                'component_a': True,
                'component_b': {'someoption': 42}
            },
            'extension_points':
            {
                'ep_a': {'someoption': 42}
            }
        }
    """

    configuration_providers = ExtensionPoint(IConfigurationProvider,
                                             caching=False)
    config_discovery = False
    loaded_providers = None

    def __init__(self):
        """Initialize Configuration"""
        self.cydra = self.compmgr
        self._data = {}
        self.loaded_providers = set()

    def get(self, key, default=None):
        return self._data.get(key, default)

    def get_component_config(self, component, default=None):
        """Find configuration node for a component

        """
        ret = self._data.setdefault('components', {}).get(component, default)

        # since setting a component to True or False is a short-hand to
        # enable/disable the component, also return the default in this case
        if isinstance(ret, bool):
            return default
        else:
            return ret

    def is_component_enabled(self, component):
        """Returns True if the component has a configuration enty or
        if we are in config discovery mode and the component has not been
        specifically disabled."""
        enabled = self._data.setdefault('components', {}).get(component, None)

        if self.config_discovery and enabled != False:
            logger.debug("Component %s enabled due to config_discovery mode",
                         component)
            return True

        return bool(enabled)

    def load(self, config=None):
        """Load configuration data into the config tree

        If config is None, the default configuration will be loaded
        """
        self.config_discovery = True

        if config is not None:
            self._load(config)
        elif self._data == {} or self._data == {'components': {}}:
            # only load default config if config is empty
            self._load(default_configuration)

        # if new configuration providers have been enabled, query them
        for provider in self.configuration_providers:
            if provider not in self.loaded_providers:
                self.loaded_providers.add(provider)
                self.load(provider.get_config())

        self.config_discovery = False

    def _load(self, config):
        plugin_paths_dirty = False

        for k, v in config.iteritems():
            if k == 'plugin_paths':
                pp = self._data.setdefault('plugin_paths', set())
                pp.update(config['plugin_paths'])
                plugin_paths_dirty = True
            else:
                if isinstance(v, dict):
                    merge(self._data.setdefault(k, dict()), v)
                elif isinstance(v, list):
                    self._data.setdefault(k, list()).extend(v)
                elif isinstance(v, set):
                    self._data.setdefault(k, set()).update(v)
                else:
                    self._data[k] = v

        if plugin_paths_dirty:
            # new plugin paths, load from those
            load_components(self.cydra, self._data.get('plugin_paths', set()))
Example #13
0
class CydraHelper(object):

    authenticator = ExtensionPoint(IUserAuthenticator)
    pubkey_store = ExtensionPoint(IPubkeyStore)

    git_binary = 'git'
    git_shell_binary = 'git-shell'

    path_matcher = re.compile(
        '^/git/(?P<project>[a-z][a-z0-9\-_]{0,31})/(?P<repository>[a-z][a-z0-9\-_]{0,31})\.git$'
    )

    def __init__(self, cyd):
        self.compmgr = self.cydra = cyd

    def can_read(self, username, virtual_path):
        repo = self.get_repository(virtual_path)
        user = self.compmgr.get_user(username=username)

        if repo is None or user is None:
            return False

        return repo.has_read_access(user)

    def can_write(self, username, virtual_path):
        repo = self.get_repository(virtual_path)
        user = self.compmgr.get_user(username=username)

        if repo is None or user is None:
            return False

        return repo.has_write_access(user)

    def check_password(self, username, password):
        user = self.compmgr.get_user(username=username)
        if user is None:
            return False

        return self.authenticator.user_password(user, password)

    def check_publickey(self, username, keyblob):
        user = self.compmgr.get_user(username=username)
        if user is None:
            return False

        return self.pubkey_store.user_has_pubkey(user, keyblob)

    def translate_path(self, virtual_path):
        repo = self.get_repository(virtual_path)
        if repo is not None:
            return repo.path

    def get_repository(self, path):
        m = self.path_matcher.match(path)
        if not m:
            return None

        project = m.group('project')
        repository = m.group('repository')

        project = self.cydra.get_project(project)
        if project is None:
            return None

        # Repository discovery
        repository = project.get_repository('git', repository)
        return repository
Example #14
0
class Repository(object):
    """Repository base"""

    path = None
    """Absolute path of the repository"""

    type = None
    """Type of the repository (string)"""

    project = None
    """Project this repository belongs to"""

    sync_participants = ExtensionPoint(ISyncParticipant)
    repository_observers = ExtensionPoint(IRepositoryObserver)

    def __init__(self, compmgr):
        """Construct a repository instance
        
        :param compmgr: Component manager (i.e. cydra instance)"""
        self.compmgr = compmgr

    def sync(self):
        """Synchronize repository with data stored in project and do maintenance work
        
        A repository should make sure post-commit hooks are registered and may collect statistics"""

        self.sync_participants.sync_repository(self)

    def delete(self, archiver=None):
        """Delete the repository"""
        if not archiver:
            archiver = self.project.get_archiver('repository_' + self.type +
                                                 '_' + self.name)

        self.repository_observers.pre_delete_repository(self)

        tmppath = os.path.join(os.path.dirname(self.path), uuid.uuid1().hex)
        os.rename(self.path, tmppath)  # POSIX guarantees this to be atomic.

        with archiver:
            archiver.add_path(
                tmppath,
                os.path.join('repository', self.type,
                             os.path.basename(self.path.rstrip('/'))))

        logger.info("Deleted repository %s of type %s: %s", self.name,
                    self.type, tmppath)

        shutil.rmtree(tmppath)

        self.repository_observers.post_delete_repository(self)

    def notify_post_commit(self, revisions):
        """A commit has occured. Notify observers"""
        self.repository_observers.repository_post_commit(self, revisions)

    #
    # Permission checks
    # Also provide sensible defaults
    #
    def can_delete(self, user):
        return self.project.get_permission(
            user, 'repository.' + self.type + '.' + self.name, 'admin')

    def can_modify_params(self, user):
        return self.project.get_permission(
            user, 'repository.' + self.type + '.' + self.name, 'admin')

    def can_read(self, user):
        return self.project.get_permission(
            user, 'repository.' + self.type + '.' + self.name, 'read')

    def can_write(self, user):
        return self.project.get_permission(
            user, 'repository.' + self.type + '.' + self.name, 'write')
Example #15
0
def create_app(cyd=None):
    """Create the web interface WSGI application"""

    if cyd is None:
        cyd = cydra.Cydra()

    from flask import Flask
    from flaskext.csrf import csrf
    from cydra.web.themes import IThemeProvider, ThemedTemplateLoader, patch_static

    app = Flask(__name__)

    # register themes
    theme_providers = ExtensionPoint(IThemeProvider, component_manager=cyd)
    app.config['cydra_themes'] = dict([(theme.name, theme) for theme in theme_providers.get_themes()])

    default_theme = cyd.config.get('web').get('default_theme')
    if default_theme is not None and default_theme in app.config['cydra_themes']:
        default_theme = app.config['cydra_themes'][default_theme]
        logger.debug("Default theme: %s", default_theme.name)
    else:
        default_theme = None
    theme_detector = ThemeDetector(default_theme)
    app.before_request(theme_detector)

    # replace default loader
    app.jinja_options = Flask.jinja_options.copy()
    app.jinja_options['loader'] = ThemedTemplateLoader(app)

    # patch static file resolver
    patch_static(app)

    # secret key for cookies
    app.secret_key = os.urandom(24)

    # consider the cydra instance to be a form of configuration
    # and therefore store it in the config dict.
    app.config['cydra'] = cyd

    # common views
    app.add_url_rule('/login', 'login', login)

    # Add shorthands to context
    app.context_processor(add_shorthands_to_context)

    # load frontend and backend
    from cydra.web.frontend import blueprint as frontend_blueprint
    patch_static(frontend_blueprint, 'frontend')
    #from cydra.web.admin import blueprint as admin_blueprint
    #patch_static(admin_blueprint, 'admin')

    app.register_blueprint(frontend_blueprint)
    #app.register_blueprint(admin_blueprint)

    # load additional blueprints
    pages = ExtensionPoint(IBlueprintProvider, component_manager=cyd)
    for bpprovider in pages:
        bp = bpprovider.get_blueprint()
        patch_static(bp, bp.name)
        app.register_blueprint(bp)

    # some utility template filters
    from cydra.web.filters import filters
    #map(app.template_filter(), filters)
    _ = [app.template_filter()(f) for f in filters]

    # prevent flask from handling exceptions
    app.debug = True

    # add CSRF protection
    csrf(app)

    # wrap in authentication middleware
    from cydra.web.wsgihelper import AuthenticationMiddleware
    app = AuthenticationMiddleware(cyd, app)

    # enable debugging for certain users
    debugusers = cyd.config.get('web').get('debug_users', [])
    if debugusers:
        from cydra.web.debugging import DebuggingMiddleware
        app = DebuggingMiddleware(app, debugusers)

    return app
Example #16
0
class Cydra(Component, ComponentManager):
    """Main point of integration

    This is the main class that figures as the component manager,
    is in charge of keeping track of a configuration and contains
    a set of convenience methods (eg. project retrieval, user lookup)"""

    datasource = ExtensionPoint(IDataSource)
    permission = ExtensionPoint(IPermissionProvider)
    translator = ExtensionPoint(IUserTranslator)
    user_store = ExtensionPoint(IUserStore)
    subject_cache = ExtensionPoint(ISubjectCache)
    project_observers = ExtensionPoint(IProjectObserver)

    _last_instance = None

    @classmethod
    def reuse_last_instance(cls, *args, **kwargs):
        """Reuse the most recently created :class:`Cydra` instance or create a new one

        By using this class method, you can reuse a previously created instance
        instead of creating a new one."""

        if cls._last_instance is not None:
            return cls._last_instance
        return cls(*args, **kwargs)

    def __init__(self, config=None):
        logging.basicConfig(level=logging.DEBUG)

        ComponentManager.__init__(self)

        load_components(self, 'cydra.config')

        self.config = Configuration(self)
        self.config.load(config)
        logger.debug("Configuration loaded: %s", repr(self.config._data))

        load_components(self)

        # Update last instance to allow instance reusing
        Cydra._last_instance = self

    def get_guest_user(self):
        return self.get_user(userid='*')

    def get_user(self, userid=None, username=None):
        """Convenience function for user retrieval"""
        if userid == '*':
            return User(self, '*', username='******', full_name='Guest')

        if userid is None and username is None:
            raise ValueError("Neither userid nor username given")

        result = None
        failed_caches = []

        # go to caches
        for cache in self.subject_cache:
            if userid is not None:
                result = cache.get_user(userid)
            elif username is not None:
                result = cache.get_user_by_name(username)

            if result is not None:
                break
            else:
                failed_caches.append(cache)

        # not found
        if result is None:
            if userid is not None:
                result = self.translator.userid_to_user(userid)
            elif username is not None:
                result = self.translator.username_to_user(username)

        if result is None:
            if userid is not None:
                # since we got a specific userid, we can construct a dummy user object
                # with the userid and dummy values for everything else.
                # This is usually a good idea since asking for a specific userid means
                # the user at least existed at some time. This provides a safe default
                # for these cases
                return User(self,
                            userid=userid,
                            full_name="N/A (" + userid + ")")
            return result

        # update caches
        for cache in failed_caches:
            cache.add_users([result])

        return result

    def create_user(self, **kwargs):
        userid = self.user_store.create_user(**kwargs)
        return self.get_user(userid=userid)

    def get_group(self, groupid):
        """Convenience function for group retrieval"""
        if groupid is None:
            raise ValueError("groupid mustn't be None")

        result = None
        failed_caches = []

        # go to caches
        for cache in self.subject_cache:
            result = cache.get_group(groupid)

            if result is not None:
                break
            else:
                failed_caches.append(cache)

        # not found
        if result is None:
            result = self.translator.groupid_to_group(groupid)

        if result is None:
            return result

        # update caches
        for cache in failed_caches:
            cache.add_groups([result])

        return result

    def get_project(self, name):
        return self.datasource.get_project(name)

    def create_project(self, projectname, owner):
        """Create a project

        :param projectname: The name of the project
        :param owner: User object of the owner of this project"""
        project = self.datasource.create_project(projectname, owner)
        self.project_observers.post_create_project(project)
        return project

    def get_projects(self, *args, **kwargs):
        return self.datasource.list_projects(*args, **kwargs)

    def get_project_names(self, *args, **kwargs):
        return self.datasource.get_project_names(*args, **kwargs)

    def get_projects_owned_by(self, *args, **kwargs):
        return self.datasource.get_projects_owned_by(*args, **kwargs)

    def get_projects_where_key_exists(self, *args, **kwargs):
        return self.datasource.get_projects_where_key_exists(*args, **kwargs)

    def get_projects_user_has_permissions_on(self, user):
        """Convenience function to retrieve all projects a user has permissions on"""
        return self.permission.get_projects_user_has_permissions_on(user)

    def get_permissions(self, user, object):
        """Convenience function for global permission enumeration"""
        return self.permission.get_permissions(None, user, object)

    def get_permission(self, user, object, permission):
        """Convenience function for permission retrieval"""
        return self.permission.get_permission(None, user, object, permission)

    def set_permission(self, user, object, permission, value):
        """Convenience function for permission retrieval"""
        return self.permission.set_permission(None, user, object, permission,
                                              value)

    def is_component_enabled(self, cls):
        component_name = cls.__module__ + '.' + cls.__name__

        return self.config.is_component_enabled(component_name)
Example #17
0
class Project(object):

    observers = ExtensionPoint(IProjectObserver)
    _repositories = ExtensionPoint(IRepositoryProvider)
    datasource = ExtensionPoint(IDataSource)
    permission = ExtensionPoint(IPermissionProvider)
    sync_participants = ExtensionPoint(ISyncParticipant)

    def __init__(self, component_manager, data):
        self.compmgr = component_manager
        self.data = data
        self.delay_save_count = 0
        self.load_time = datetime.datetime.now()

    @property
    def name(self):
        return self.data['name']

    @property
    def owner(self):
        return self.compmgr.get_user(userid=self.data['owner'])

    def get_repository(self, repository_type, name):
        """Convenience function for direct repository lookup"""
        for repotype in self._repositories:
            if repotype.repository_type == repository_type:
                return repotype.get_repository(self, name)

    def get_repositories(self):
        """Returns a list of all repositories in this project"""
        repos = []
        for repotype in self.get_repository_types():
            repos.extend(repotype.get_repositories(self))
        return repos

    def get_repository_type(self, repository_type):
        """Convenience function for direct repository type retrieval"""
        for repotype in self._repositories:
            if repotype.repository_type == repository_type:
                return repotype

    def get_repository_types(self):
        return self._repositories

    def get_permissions(self, user, obj):
        """Convenience function for permission enumeration"""
        return self.permission.get_permissions(self, user, obj)

    def get_permission(self, user, obj, permission):
        """Convenience function for permission retrieval"""
        return self.permission.get_permission(self, user, obj, permission)

    def set_permission(self, user, obj, permission, value):
        """Convenience function for permission retrieval"""
        return self.permission.set_permission(self, user, obj, permission,
                                              value)

    def get_group_permissions(self, group, obj):
        """Convenience function for permission enumeration"""
        return self.permission.get_group_permissions(self, group, obj)

    def get_group_permission(self, group, obj, permission):
        """Convenience function for permission retrieval"""
        return self.permission.get_group_permission(self, group, obj,
                                                    permission)

    def set_group_permission(self, group, obj, permission, value):
        """Convenience function for permission retrieval"""
        return self.permission.set_group_permission(self, group, obj,
                                                    permission, value)

    def sync_repositories(self):
        """Sync all repositories of this project

        :returns: False if any of the repositories returned False, True otherwise"""
        # You might wonder why this function exists instead of the repository provider
        # implementing SyncParticipant. Since the Repository class contains sync, all
        # repositories provide this function and we can handle all of them here
        res = True

        for repo in self.get_repositories():
            if repo.sync(
            ) == False:  # Don't use not here, None should not be considered a failure
                res = False

        return res

    def sync(self):
        """Synchronize the project"""
        return not any([
            x == False for x in self.sync_participants.sync_project(self)
        ])  # Don't use not here, None should not be considered a failure

    def get_archiver(self, filename):
        """Get an archiver for this project"""
        path = self.compmgr.config.get('archive_path', None)
        if not path:
            return NoopArchiver()

        path = os.path.join(path, self.name)
        if not os.path.exists(path):
            os.mkdir(path)

        path = os.path.join(
            path, filename + '_' +
            datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + '.tar')
        return TarArchiver(path)

    def delay_save(self):
        self.delay_save_count += 1

    def undelay_save(self):
        self.delay_save_count -= 1

        if self.delay_save_count == 0:
            self.save()
        elif self.delay_save_count < 0:
            raise Exception("More undelay than delay saves")

    def save(self):
        if self.delay_save_count > 0:
            return

        if datetime.datetime.now() - self.load_time > datetime.timedelta(
                seconds=5):
            logger.warning(
                "Warning, time elapsed between loading and saving project %s was %s",
                self.name, str(datetime.datetime.now() - self.load_time))

        self.datasource.save_project(self)

    def delete(self, archiver=None):
        if not archiver:
            archiver = self.get_archiver('project')

        with archiver:
            # backup project data
            archiver.dump_as_file(self.data, "project_data")

            # delete everything in the project
            self.observers.pre_delete_project(self, archiver)

        self.datasource.delete_project(self)

    def __eq__(self, other):
        return self.compmgr == other.compmgr and self.name == other.name

    def __hash__(self):
        return hash((self.compmgr, self.name))
Example #18
0
File: hg.py Project: mensi/cydra
class HgRepository(Repository):

    permission = ExtensionPoint(IPermissionProvider)

    def __init__(self, component_manager, base, project, name):
        """Construct an instance"""
        super(HgRepository, self).__init__(component_manager)

        self.project = project
        self.name = name
        self.base = base
        self.type = 'hg'

        self.path = os.path.abspath(os.path.join(self.base, project.name,
                                                 name))
        self.hgrc_path = os.path.join(self.path, '.hg', 'hgrc')

        # ensure this repository actually exists
        if not os.path.exists(self.path):
            raise UnknownRepository(repository_name=name,
                                    project_name=project.name,
                                    repository_type='hg')

    def has_read_access(self, user):
        return self.project.get_permission(user, 'repository.hg.' + self.name,
                                           'read')

    def has_write_access(self, user):
        return self.project.get_permission(user, 'repository.hg.' + self.name,
                                           'write')

    def _get_config(self):
        cp = ConfigParser.RawConfigParser()

        if os.path.exists(self.hgrc_path):
            try:
                cp.read(self.hgrc_path)
            except:
                logger.exception("Unable to parse hgrc file %s",
                                 self.hgrc_path)
                raise CydraError('Cannot parse existing hgrc file')
        else:
            logger.info("No hgrc file found at %s", self.hgrc_path)

        return cp

    def _set_config(self, config):
        try:
            with open(self.hgrc_path, 'wb') as f:
                config.write(f)
        except Exception:
            logger.exception("Unable to write hgrc file %s", self.hgrc_path)
            raise CydraError('Unable to write hgrc file')

    def get_param(self, param):
        cp = self._get_config()

        if param in ['contact', 'description']:
            if cp.has_option('web', param):
                return cp.get('web', param)

    def set_params(self, **params):
        if not params:
            return

        cp = self._get_config()

        if not cp.has_section('web'):
            cp.add_section('web')

        for param in ['contact', 'description']:
            if param in params:
                if params[param] is None and cp.has_option('web', param):
                    cp.remove_option('web', param)
                elif params[param] is not None:
                    cp.set('web', param, params[param])

        self._set_config(cp)

    def sync(self):
        """Installs necessary hooks"""
        from jinja2 import Template

        tpl = self.compmgr.config.get_component_config(
            'cydra.repository.git.HgRepositories', {}).get('commit_script')
        if tpl:
            with open(tpl, 'r') as f:
                template = Template(f.read())
        else:
            from pkg_resources import resource_string
            template = Template(
                resource_string('cydra.repository', 'scripts/hg_commit.sh'))

        hook = template.render(project=self.project, repository=self)
        hookpath = os.path.join(self.path, '.hg', 'cydra_commit_hook.sh')
        with open(hookpath, 'w') as f:
            f.write(hook)
            mode = os.fstat(f.fileno()).st_mode
            mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
            os.fchmod(f.fileno(), mode)

        # register hook
        cp = self._get_config()
        if not cp.has_section('hooks'):
            cp.add_section('hooks')
        cp.set('hooks', 'commit.cydra', hookpath)
        cp.set('hooks', 'changegroup.cydra', hookpath)
        self._set_config(cp)

        super(HgRepository, self).sync()
Example #19
0
class CydraHelper(object):

    authenticator = ExtensionPoint(IUserAuthenticator)
    pubkey_store = ExtensionPoint(IPubkeyStore)

    git_binary = 'git'
    git_shell_binary = 'git-shell'

    def __init__(self, cyd):
        self.compmgr = self.cydra = cyd

        repoconfig = cyd.config.get_component_config(
            'cydra.repository.git.GitRepositories', {})
        if 'base' not in repoconfig:
            raise Exception("git base path not configured")
        self.gitbase = repoconfig['base']

        self.config = cyd.config.get_component_config(
            'cydraplugins.gitserverglue.GitServerGlue', {})

    def can_read(self, username, path_info):
        project = path_info.get('cydra_project')
        repo = path_info.get('cydra_repository')

        if username is None:
            user = self.compmgr.get_user(userid='*')
        else:
            user = self.compmgr.get_user(username=username)

        if repo is None and project is not None:
            return project.get_permission(user, '*', 'read')

        if repo is None or user is None:
            return False

        return repo.has_read_access(user)

    def can_write(self, username, path_info):
        repo = path_info.get('cydra_repository')

        if username is None:
            user = self.compmgr.get_user(userid='*')
        else:
            user = self.compmgr.get_user(username=username)

        if repo is None or user is None:
            return False

        return repo.has_write_access(user)

    def check_password(self, username, password):
        user = self.compmgr.get_user(username=username)
        if user is None:
            return False

        return self.authenticator.user_password(user, password)

    def check_publickey(self, username, keyblob):
        user = self.compmgr.get_user(username=username)
        if user is None:
            return False

        return self.pubkey_store.user_has_pubkey(user, keyblob)

    def path_lookup(self, url, protocol_hint=None):
        res = {
            'repository_base_fs_path': None,
            'repository_base_url_path': None,
            'repository_fs_path': None
        }

        pathparts = url.lstrip('/').split('/')
        project = None
        reponame = None

        if protocol_hint == 'ssh':
            # /git/project/reponame.git
            if (len(pathparts) < 2 or pathparts[0] != 'git'
                    or not cydra.project.is_valid_project_name(pathparts[1])):
                return
            res['cydra_project'] = project = self.cydra.get_project(
                pathparts[1])
            res['repository_base_url_path'] = '/git/' + project.name + '/'
            reponame = pathparts[2] if len(pathparts) >= 3 else None

        else:
            # /project/reponame.git resp. /some/prefix/project/reponame.git
            # if gitserverglue is behind a reverse proxy
            prefixparts = []
            if 'http_url_prefix' in self.config:
                prefixparts = self.config['http_url_prefix'].lstrip('/').split(
                    '/')

            if len(pathparts) - len(
                    prefixparts) < 1 or not cydra.project.is_valid_project_name(
                        pathparts[len(prefixparts)]):
                return
            res['cydra_project'] = project = self.cydra.get_project(
                pathparts[len(prefixparts)])

            if project is None:
                return
            res['repository_base_url_path'] = '/' + '/'.join(
                prefixparts + [project.name]) + '/'

            reponame = pathparts[
                len(prefixparts) +
                1] if len(pathparts) >= len(prefixparts) + 2 else None

        if project is None:
            return

        res['repository_base_fs_path'] = os.path.join(self.gitbase,
                                                      project.name) + '/'

        if reponame is not None and reponame != '':
            if reponame.endswith('.git'):
                reponame = reponame[:-4]
            res['cydra_repository'] = repo = project.get_repository(
                'git', reponame)
            if repo is None:
                return res

            res['repository_fs_path'] = repo.path
            res['repository_clone_urls'] = {}
            if 'http_url_base' in self.config:
                res['repository_clone_urls']['http'] = self.config[
                    'http_url_base'] + '/' + project.name + '/' + repo.name + '.git'

            if 'ssh_url_base' in self.config:
                res['repository_clone_urls']['ssh'] = self.config[
                    'ssh_url_base'] + '/git/' + project.name + '/' + repo.name + '.git'

        return res