Example #1
0
def test_remove(run):
    """Test removing packages."""
    remove(['package1', 'package2'])
    run.assert_has_calls(
        [call('packages', ['remove', '--packages', 'package1', 'package2'])])

    run.reset_mock()
    run.side_effect = ActionError()
    remove(['package1'])
    run.assert_has_calls(
        [call('packages', ['remove', '--packages', 'package1'])])
def test_delete_repository_fail_view(rf):
    """Test that failed repository deletion sends correct error message."""

    with patch('plinth.modules.gitweb.delete_repo',
               side_effect=ActionError('Error')):
        response, messages = make_request(rf.post(''), views.delete,
                                          name=EXISTING_REPOS[0]['name'])

        assert list(
            messages)[0].message == 'Could not delete {}: Error'.format(
                EXISTING_REPOS[0]['name'])
        assert response.status_code == 302
def test_disable_samba_share_failed_view(rf):
    """Test that share disabling failure sends correct error message."""
    form_data = {'filesystem_type': 'ext4', 'open_share': 'disable'}
    mount_point = urllib.parse.quote('/')
    error_message = 'Unsharing failed'
    with patch('plinth.modules.samba.delete_share',
               side_effect=ActionError(error_message)):
        response, messages = make_request(rf.post('', data=form_data),
                                          views.share,
                                          mount_point=mount_point)

        assert list(messages)[
            0].message == 'Error disabling share: {0}'.format(error_message)
        assert response.status_code == 302
        assert response.url == urls.reverse('samba:index')
def test_create_repo_failed_view(rf):
    """Test that repo creation failure sends correct error message."""
    with patch('plinth.modules.gitweb.create_repo',
               side_effect=ActionError('Error')):
        form_data = {
            'gitweb-name': 'something_other',
            'gitweb-description': '',
            'gitweb-owner': ''
        }
        request = rf.post(urls.reverse('gitweb:create'), data=form_data)
        view = views.CreateRepoView.as_view()
        response, messages = make_request(request, view)

        assert list(messages)[
            0].message == 'An error occurred while creating the repository.'
        assert response.status_code == 302
def test_edit_repository_failed_view(rf):
    """Test that failed repo editing sends correct error message."""
    with patch('plinth.modules.gitweb.edit_repo',
               side_effect=ActionError('Error')):
        form_data = {
            'gitweb-name': 'something_other',
            'gitweb-description': 'test-description',
            'gitweb-owner': 'test-owner'
        }
        request = rf.post(
            urls.reverse('gitweb:edit',
                         kwargs={'name': EXISTING_REPOS[0]['name']}),
            data=form_data)
        view = views.EditRepoView.as_view()
        response, messages = make_request(request, view, **EXISTING_REPOS[0])

        assert list(
            messages)[0].message == 'An error occurred during configuration.'
        assert response.status_code == 302
Example #6
0
        cmd += list(options)  # No escaping necessary

    # Contract 1: commands can run via sudo.
    if run_as_root:
        cmd = ['sudo', '-n'] + cmd
    elif become_user:
        cmd = ['sudo', '-n', '-u', become_user] + cmd

    LOGGER.info('Executing command - %s', cmd)

    # Contract 3C: don't interpret shell escape sequences.
    # Contract 5 (and 6-ish).
    proc = subprocess.Popen(cmd,
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            shell=False)

    if not async:
        output, error = proc.communicate(input=input)
        output, error = output.decode(), error.decode()
        if proc.returncode != 0:
            LOGGER.error('Error executing command - %s, %s, %s', cmd, output,
                         error)
            raise ActionError(action, output, error)

        return output
    else:
        return proc
Example #7
0
def _run(action, options=None, input=None, run_in_background=False,
         run_as_root=False, become_user=None, log_error=True):
    """Safely run a specific action as a normal user or root.

    Actions are pulled from the actions directory.

    - options are added to the action command.

    - input: data (as bytes) that will be sent to the action command's stdin.

    - run_in_background: run asynchronously or wait for the command to
      complete.

    - run_as_root: execute the command through sudo.

    """
    if options is None:
        options = []

    # Contract 3A and 3B: don't call anything outside of the actions directory.
    if os.sep in action:
        raise ValueError('Action cannot contain: ' + os.sep)

    cmd = os.path.join(cfg.actions_dir, action)
    if not os.path.realpath(cmd).startswith(cfg.actions_dir):
        raise ValueError('Action has to be in directory %s' % cfg.actions_dir)

    # Contract 3C: interpret shell escape sequences as literal file names.
    # Contract 3E: fail if the action doesn't exist or exists elsewhere.
    if not os.access(cmd, os.F_OK):
        raise ValueError('Action must exist in action directory.')

    cmd = [cmd]

    # Contract: 3C, 3D: don't allow shell special characters in
    # options be interpreted by the shell.  When using
    # subprocess.Popen with list invocation and not shell invocation,
    # escaping is unnecessary as each argument is passed directly to
    # the command and not parsed by a shell.
    if options:
        if not isinstance(options, (list, tuple)):
            raise ValueError('Options must be list or tuple.')

        cmd += list(options)  # No escaping necessary

    # Contract 1: commands can run via sudo.
    sudo_call = []
    if run_as_root:
        sudo_call = ['sudo', '-n']
    elif become_user:
        sudo_call = ['sudo', '-n', '-u', become_user]

    if cfg.develop and sudo_call:
        # Passing 'env' does not work with sudo, so append the PYTHONPATH
        # as part of the command
        sudo_call += ['PYTHONPATH=%s' % cfg.file_root]

    if sudo_call:
        cmd = sudo_call + cmd

    _log_command(cmd)

    # Contract 3C: don't interpret shell escape sequences.
    # Contract 5 (and 6-ish).
    kwargs = {
        'stdin': subprocess.PIPE,
        'stdout': subprocess.PIPE,
        'stderr': subprocess.PIPE,
        'shell': False,
    }
    if cfg.develop:
        # In development mode pass on local pythonpath to access Plinth
        kwargs['env'] = {'PYTHONPATH': cfg.file_root}

    proc = subprocess.Popen(cmd, **kwargs)

    if not run_in_background:
        output, error = proc.communicate(input=input)
        output, error = output.decode(), error.decode()
        if proc.returncode != 0:
            if log_error:
                logger.error('Error executing command - %s, %s, %s', cmd,
                             output, error)
            raise ActionError(action, output, error)

        return output

    return proc
Example #8
0
def _run(action,
         options=None,
         input=None,
         run_in_background=False,
         run_as_root=False,
         become_user=None,
         log_error=True):
    """Safely run a specific action as a normal user or root.

    Actions are pulled from the actions directory.
    - options are added to the action command.
    - input: data (as bytes) that will be sent to the action command's stdin.
    - run_in_background: run asynchronously or wait for the command to
      complete.
    - run_as_root: execute the command through sudo.
    """
    if options is None:
        options = []

    # Contract 3A and 3B: don't call anything outside of the actions directory.
    if os.sep in action:
        raise ValueError('Action cannot contain: ' + os.sep)

    cmd = os.path.join(cfg.actions_dir, action)
    if not os.path.realpath(cmd).startswith(cfg.actions_dir):
        raise ValueError('Action has to be in directory %s' % cfg.actions_dir)

    # Contract 3C: interpret shell escape sequences as literal file names.
    # Contract 3E: fail if the action doesn't exist or exists elsewhere.
    if not os.access(cmd, os.F_OK):
        raise ValueError('Action must exist in action directory.')

    cmd = [cmd]

    # Contract: 3C, 3D: don't allow shell special characters in
    # options be interpreted by the shell.  When using
    # subprocess.Popen with list invocation and not shell invocation,
    # escaping is unnecessary as each argument is passed directly to
    # the command and not parsed by a shell.
    if options:
        if not isinstance(options, (list, tuple)):
            raise ValueError('Options must be list or tuple.')

        cmd += list(options)  # No escaping necessary

    # Contract 1: commands can run via sudo.
    if run_as_root:
        cmd = ['sudo', '-n'] + cmd
    elif become_user:
        cmd = ['sudo', '-n', '-u', become_user] + cmd

    LOGGER.info('Executing command - %s', cmd)

    # Contract 3C: don't interpret shell escape sequences.
    # Contract 5 (and 6-ish).
    proc = subprocess.Popen(cmd,
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            shell=False)

    if not run_in_background:
        output, error = proc.communicate(input=input)
        output, error = output.decode(), error.decode()
        if proc.returncode != 0:
            if log_error:
                LOGGER.error('Error executing command - %s, %s, %s', cmd,
                             output, error)
            raise ActionError(action, output, error)

        return output
    else:
        return proc