Exemple #1
0
def add_submission_delegate(username):
    """
    Adds a submission delegate for a user account. Submission delegates are 
    other users who are allowed to submit jobs on behalf of this user.

    :param username: The user's username.
    :jsonparam string user_name: The submission delegate's username.
    """
    user = User.by_user_name(username)
    if user is None:
        raise NotFound404('User %s does not exist' % username)
    if not user.can_edit(identity.current.user):
        raise Forbidden403('Cannot edit user %s' % user)
    data = read_json_request(request)
    if 'user_name' not in data:
        raise BadRequest400(
            'Missing "user_name" key to specify submission delegate')
    submission_delegate = User.by_user_name(data['user_name'])
    if submission_delegate is None:
        raise BadRequest400('Submission delegate %s does not exist' %
                            data['user_name'])
    try:
        user.add_submission_delegate(submission_delegate, service=u'HTTP')
    except NoChangeException as e:
        raise Conflict409(unicode(e))
    return 'Added', 201
Exemple #2
0
def provision_system(fqdn):
    system = _get_system_by_FQDN(fqdn)
    if not system.can_configure_netboot(identity.current.user):
        raise Forbidden403('Cannot provision system')
    data = read_json_request(request)
    with convert_internal_errors():
        if not data['distro_tree'] or 'id' not in data['distro_tree']:
            raise ValueError('No distro tree specified')
        distro_tree = DistroTree.by_id(data['distro_tree']['id'])
        user = identity.current.user
        if user.rootpw_expired:
            raise ValueError('Your root password has expired, you must '
                    'change or clear it in order to provision.')
            redirect(u"/view/%s" % system.fqdn)
        install_options = system.manual_provision_install_options(distro_tree)\
                .combined_with(InstallOptions.from_strings(data.get('ks_meta'),
                    data.get('koptions'), data.get('koptions_post')))
        if 'ks' not in install_options.kernel_options:
            kickstart = generate_kickstart(install_options,
                    distro_tree=distro_tree, system=system, user=user)
            install_options.kernel_options['ks'] = kickstart.link
        system.configure_netboot(distro_tree,
                install_options.kernel_options_str, service=u'HTTP')
        system.record_activity(user=identity.current.user, service=u'HTTP',
                action=u'Provision', field=u'Distro Tree',
                new=unicode(distro_tree))
        if data.get('reboot'):
            system.action_power(action=u'reboot', service=u'HTTP')
    # in future "installations" will be a real thing in our model,
    # but for now we have nothing to return
    return 'Provisioned', 201
Exemple #3
0
def exclude_user(group_name):
    """
    Exclude a user from an inverted group. Then the user will not have the group
    membership.

    :param group_name: Group's name.
    :jsonparam string user_name: User's username.

    """
    u = identity.current.user
    data = read_json_request(request)
    group = _get_group_by_name(group_name, lockmode='update')
    if not group.can_modify_membership(identity.current.user):
        raise Forbidden403('Cannot edit membership of group %s' % group_name)
    if group.membership_type == GroupMembershipType.normal:
        raise NotFound404('Normal group %s do not have excluded users' %
                          group_name)
    if 'user_name' not in data:
        raise BadRequest400('User not specified')
    user = _get_user_by_username(data['user_name'])
    if user in group.users:
        if not group.can_exclude_member(u, user.id):
            raise Forbidden403('Cannot exclude user %s from group %s' %
                               (user, group_name))
        with convert_internal_errors():
            group.exclude_user(user, agent=identity.current.user)
    else:
        raise Conflict409('User %s is already excluded from group %s' %
                          (user.user_name, group_name))
    return '', 204
Exemple #4
0
def update_recipeset_by_taskspec(taskspec):
    """
    Updates the attributes of a recipe set identified by a taskspec. The valid type
    of a taskspec is either J(job) or RS(recipe-set). If a taskspec format is
    J:<id>, all the recipe sets in this job will be updated. The request must be
    :mimetype:`application/json`.

    :param taskspec: A taskspec argument that identifies a job or recipe set.
    :jsonparam string priority: Priority for the recipe set. Must be one of 
      'Low', 'Medium', 'Normal', 'High', or 'Urgent'. This can only be changed 
      while a recipe set is still queued. Job owners can generally only 
      *decrease* the priority of their recipe set, queue admins can increase 
      it.
    :jsonparam boolean waived: If true, the recipe set will be waived, regardless
      of its result.
    """
    if not taskspec.startswith(('J', 'RS')):
        raise BadRequest400('Taskspec type must be one of [J, RS]')
    try:
        obj = TaskBase.get_by_t_id(taskspec)
    except BeakerException as exc:
        raise NotFound404(unicode(exc))
    data = read_json_request(request)
    if isinstance(obj, Job):
        for rs in obj.recipesets:
            _update_recipeset(rs, data)
    elif isinstance(obj, RecipeSet):
        _update_recipeset(obj, data)
    return jsonify(obj.__json__())
Exemple #5
0
def add_system_access_policy_rule(fqdn):
    system = _get_system_by_FQDN(fqdn)
    if not system.can_edit_policy(identity.current.user):
        raise Forbidden403('Cannot edit system policy')
    if system.custom_access_policy:
        policy = system.custom_access_policy
    else:
        policy = system.custom_access_policy = SystemAccessPolicy()
    rule = read_json_request(request)

    if rule['user']:
        user = User.by_user_name(rule['user'])
        if not user:
            raise BadRequest400("User '%s' does not exist" % rule['user'])
    else:
        user = None

    if rule['group']:
        try:
            group = Group.by_name(rule['group'])
        except NoResultFound:
            raise BadRequest400("Group '%s' does not exist" % rule['group'])
    else:
        group = None

    try:
        permission = SystemPermission.from_string(rule['permission'])
    except ValueError:
        raise BadRequest400
    new_rule = policy.add_rule(user=user, group=group,
            everybody=rule['everybody'], permission=permission)
    system.record_activity(user=identity.current.user, service=u'HTTP',
            field=u'Access Policy Rule', action=u'Added',
            new=repr(new_rule))
    return '', 204
Exemple #6
0
def add_group_membership(group_name):
    """
    Add a user to a group.

    :param group_name: Group's name.
    :jsonparam string user_name: User's username.
    :jsonparam boolean is_owner: If true, the given user will become one of the
      group owners.

    """
    u = identity.current.user
    data = read_json_request(request)
    group = _get_group_by_name(group_name, lockmode='update')
    if not group.can_modify_membership(identity.current.user):
        raise Forbidden403('Cannot edit membership of group %s' % group_name)
    if 'user_name' not in data:
        raise BadRequest400('User not specified')
    user = _get_user_by_username(data['user_name'])
    is_owner = data.get('is_owner', False)
    if user not in group.users:
        group.add_member(user, is_owner=is_owner, agent=identity.current.user)
        mail.group_membership_notify(user, group, agent=u, action='Added')
    else:
        raise Conflict409('User %s is already a member of group %s' % (user.user_name, group_name))
    return '', 204
Exemple #7
0
def update_recipeset_status(id):
    """
    Updates the status of a recipe set. The request must be :mimetype:`application/json`.

    Currently the only allowed value for status is 'Cancelled', which has the 
    effect of cancelling all recipes in the recipe set that have not finished yet.

    :param id: ID of the recipe set.
    :jsonparam string status: The new status. Must be 'Cancelled'.
    :jsonparam string msg: A message describing the reason for updating the status.
    """
    recipeset = _get_rs_by_id(id)
    if not recipeset.can_cancel(identity.current.user):
        raise Forbidden403('Cannot update recipe set status')
    data = read_json_request(request)
    if 'status' not in data:
        raise BadRequest400('Missing status')
    status = TaskStatus.from_string(data['status'])
    msg = data.get('msg', None) or None
    if status != TaskStatus.cancelled:
        raise BadRequest400('Status must be "Cancelled"')
    with convert_internal_errors():
        recipeset.record_activity(user=identity.current.user,
                                  service=u'HTTP',
                                  field=u'Status',
                                  action=u'Cancelled')
        recipeset.cancel(msg=msg)
    return '', 204
Exemple #8
0
def _create_keystone_trust(user):
    if not config.get('openstack.identity_api_url'):
        raise BadRequest400("OpenStack Integration is not enabled")
    if not user.can_edit_keystone_trust(identity.current.user):
        raise Forbidden403('Cannot edit Keystone trust of user %s' %
                           user.username)
    data = read_json_request(request)
    if 'openstack_username' not in data:
        raise BadRequest400('No OpenStack username specified')
    if 'openstack_password' not in data:
        raise BadRequest400('No OpenStack password specified')
    if 'openstack_project_name' not in data:
        raise BadRequest400('No OpenStack project name specified')
    try:
        trust_id = dynamic_virt.create_keystone_trust(
            data['openstack_username'], data['openstack_password'],
            data['openstack_project_name'])
    except ValueError as err:
        raise BadRequest400(
            u'Could not authenticate with OpenStack using your credentials: %s'
            % unicode(err))
    user.openstack_trust_id = trust_id
    user.record_activity(user=identity.current.user,
                         service=u'HTTP',
                         field=u'OpenStack Trust ID',
                         action=u'Changed')
    return jsonify({'openstack_trust_id': trust_id})
Exemple #9
0
def exclude_user(group_name):
    """
    Exclude a user from an inverted group. Then the user will not have the group
    membership.

    :param group_name: Group's name.
    :jsonparam string user_name: User's username.

    """
    u = identity.current.user
    data = read_json_request(request)
    group = _get_group_by_name(group_name, lockmode='update')
    if not group.can_modify_membership(identity.current.user):
        raise Forbidden403('Cannot edit membership of group %s' % group_name)
    if group.membership_type == GroupMembershipType.normal:
        raise NotFound404('Normal group %s do not have excluded users' % group_name)
    if 'user_name' not in data:
        raise BadRequest400('User not specified')
    user = _get_user_by_username(data['user_name'])
    if user in group.users:
        if not group.can_exclude_member(u, user.id):
            raise Forbidden403('Cannot exclude user %s from group %s' % (user, group_name))
        with convert_internal_errors():
            group.exclude_user(user, agent=identity.current.user)
    else:
        raise Conflict409('User %s is already excluded from group %s' %
                (user.user_name, group_name))
    return '', 204
Exemple #10
0
def add_group_membership(group_name):
    """
    Add a user to a group.

    :param group_name: Group's name.
    :jsonparam string user_name: User's username.
    :jsonparam boolean is_owner: If true, the given user will become one of the
      group owners.

    """
    u = identity.current.user
    data = read_json_request(request)
    group = _get_group_by_name(group_name, lockmode='update')
    if not group.can_modify_membership(identity.current.user):
        raise Forbidden403('Cannot edit membership of group %s' % group_name)
    if 'user_name' not in data:
        raise BadRequest400('User not specified')
    user = _get_user_by_username(data['user_name'])
    if user.removed:
        raise BadRequest400('Cannot add deleted user %s to group' %
                            user.user_name)
    is_owner = data.get('is_owner', False)
    if user not in group.users:
        group.add_member(user, is_owner=is_owner, agent=identity.current.user)
        mail.group_membership_notify(user, group, agent=u, action='Added')
    else:
        raise Conflict409('User %s is already a member of group %s' %
                          (user.user_name, group_name))
    return '', 204
Exemple #11
0
def update_task(task_id):
    """
    Updates a task - only handles disabling at this time.

    :param task_id: The task id to update/disable
    :jsonparam bool disabled: Whether the task should be disabled.
    :status 200: Task was successfully updated/disabled
    :status 404: Task was not found (to be disabled)
    """
    try:
        task = Task.by_id(task_id)
    except DatabaseLookupError as e:
        # This should be NotFound404 but due to still using cherrypy
        # 404's are handled there which then will then do a GET /tasks/id
        # which will resolve correctly, which isn't desired
        raise NotFound404('Task %s does not exist' % task_id)

    data = read_json_request(request)

    if data:
        with convert_internal_errors():
            if data.get('disabled', False) and task.valid:
                task.disable()

    response = jsonify(task.to_dict())

    return response
Exemple #12
0
def save_system_access_policy(fqdn):
    system = _get_system_by_FQDN(fqdn)
    if not system.can_edit_policy(identity.current.user):
        raise Forbidden403('Cannot edit system policy')
    if system.custom_access_policy:
        policy = system.custom_access_policy
    else:
        policy = system.custom_access_policy = SystemAccessPolicy()
    data = read_json_request(request)
    # Figure out what is added, what is removed.
    # Rules are immutable, so if it has an id it is unchanged, 
    # if it has no id it is new.
    kept_rule_ids = frozenset(r['id'] for r in data['rules'] if 'id' in r)
    removed = []
    for old_rule in policy.rules:
        if old_rule.id not in kept_rule_ids:
            removed.append(old_rule)
    for old_rule in removed:
        system.record_activity(user=identity.current.user, service=u'HTTP',
                field=u'Access Policy Rule', action=u'Removed',
                old=repr(old_rule))
        policy.rules.remove(old_rule)
    for rule in data['rules']:
        if 'id' not in rule:
            user = User.by_user_name(rule['user']) if rule['user'] else None
            group = Group.by_name(rule['group']) if rule['group'] else None
            permission = SystemPermission.from_string(rule['permission'])
            new_rule = policy.add_rule(user=user, group=group,
                    everybody=rule['everybody'], permission=permission)
            system.record_activity(user=identity.current.user, service=u'HTTP',
                    field=u'Access Policy Rule', action=u'Added',
                    new=repr(new_rule))
    return '', 204
Exemple #13
0
def update_recipe(id):
    """
    Updates the attributes of a recipe. The request must be 
    :mimetype:`application/json`.

    :param id: Recipe's id.
    :jsonparam string whiteboard: Whiteboard of the recipe.
    :status 200: Recipe was updated.
    :status 400: Invalid data was given.
    """

    recipe = _get_recipe_by_id(id)
    if not recipe.can_edit(identity.current.user):
        raise Forbidden403('Cannot edit recipe %s' % recipe.id)
    data = read_json_request(request)
    with convert_internal_errors():
        if 'whiteboard' in data:
            new_whiteboard = data['whiteboard']
            if new_whiteboard != recipe.whiteboard:
                _record_activity(recipe, u'Whiteboard', recipe.whiteboard,
                                 new_whiteboard)
                recipe.whiteboard = new_whiteboard
        if 'reviewed' in data:
            recipe.set_reviewed_state(identity.current.user,
                                      bool(data['reviewed']))
    return jsonify(recipe.__json__())
Exemple #14
0
def update_recipe(id):
    """
    Updates the attributes of a recipe. The request must be 
    :mimetype:`application/json`.

    :param id: Recipe's id.
    :jsonparam string whiteboard: Whiteboard of the recipe.
    :status 200: Recipe was updated.
    :status 400: Invalid data was given.
    """

    recipe = _get_recipe_by_id(id)
    if not recipe.can_edit(identity.current.user):
        raise Forbidden403('Cannot edit recipe %s' % recipe.id)
    data = read_json_request(request)
    with convert_internal_errors():
        if 'whiteboard' in data:
            new_whiteboard = data['whiteboard']
            if new_whiteboard != recipe.whiteboard:
                _record_activity(recipe, u'Whiteboard', recipe.whiteboard,
                    new_whiteboard)
                recipe.whiteboard = new_whiteboard
        if 'reviewed' in data:
            recipe.set_reviewed_state(identity.current.user, bool(data['reviewed']))
    return jsonify(recipe.__json__())
Exemple #15
0
def update_reservation_request(id):
    """
    Updates the reservation request of a recipe. The request must be 
    :mimetype:`application/json`.

    :param id: Recipe's id.
    :jsonparam boolean reserve: Whether the system will be reserved at the end
      of the recipe. If true, the system will be reserved. If false, the system
      will not be reserved.
    :jsonparam int duration: Number of seconds to reserve the system.
    :jsonparam string when: Circumstances under which the system will be 
      reserved. Valid values are:

      onabort
        If the recipe status is Aborted.
      onfail
        If the recipe status is Aborted, or the result is Fail.
      onwarn
        If the recipe status is Aborted, or the result is Fail or Warn.
      always
        Unconditionally.
    """

    recipe = _get_recipe_by_id(id)
    if not recipe.can_update_reservation_request(identity.current.user):
        raise Forbidden403(
            'Cannot update the reservation request of recipe %s' % recipe.id)
    data = read_json_request(request)
    if 'reserve' not in data:
        raise BadRequest400('No reserve specified')
    with convert_internal_errors():
        if data['reserve']:
            if not recipe.reservation_request:
                recipe.reservation_request = RecipeReservationRequest()
            if 'duration' in data:
                duration = int(data['duration'])
                if duration > MAX_SECONDS_PROVISION:
                    raise BadRequest400(
                        'Reservation time exceeds maximum time of %s hours' %
                        MAX_HOURS_PROVISION)
                old_duration = recipe.reservation_request.duration
                recipe.reservation_request.duration = duration
                _record_activity(recipe, u'Reservation Request', old_duration,
                                 duration)
            if 'when' in data:
                old_condition = recipe.reservation_request.when
                new_condition = RecipeReservationCondition.from_string(
                    data['when'])
                recipe.reservation_request.when = new_condition
                _record_activity(recipe, u'Reservation Condition',
                                 old_condition, new_condition)
            session.flush()  # to ensure the id is populated
            return jsonify(recipe.reservation_request.__json__())
        else:
            if recipe.reservation_request:
                session.delete(recipe.reservation_request)
                _record_activity(recipe, u'Reservation Request',
                                 recipe.reservation_request.duration, None)
            return jsonify(RecipeReservationRequest.empty_json())
Exemple #16
0
def create_group():
    """
    Creates a new user group in Beaker. The request must be 
    :mimetype:`application/json`.

    :jsonparam string group_name: Symbolic name for the group.
    :jsonparam string display_name: Human-friendly display name for the group.
    :jsonparam string description: Description of the group.
    :jsonparam string root_password: Optional root password for group jobs.
      If this is not set, group jobs will use the root password preferences of 
      the job submitter.
    :jsonparam string membership_type: Specifies how group membership is populated.
      Possible values are:

      * normal: Group is initially empty, members are explicitly added and removed by
        group owner.
      * ldap: Membership is populated from the LDAP group with the same group name.
      * inverted: Group contains all Beaker users *except* users who have been explicitly
        excluded by the group owner.

    :status 201: The group was successfully created.
    """
    user = identity.current.user
    data = read_json_request(request)
    if 'group_name' not in data:
        raise BadRequest400('Missing group_name key')
    if 'display_name' not in data:
        raise BadRequest400('Missing display_name key')
    # for backwards compatibility
    if data.pop('ldap', False):
        data['membership_type'] = 'ldap'
    try:
        Group.by_name(data['group_name'])
    except NoResultFound:
        pass
    else:
        raise Conflict409("Group '%s' already exists" % data['group_name'])
    with convert_internal_errors():
        group = Group.lazy_create(group_name=data['group_name'])
        group.display_name = data['display_name']
        group.description = data.get('description')
        group.root_password = data.get('root_password')
        session.add(group)
        group.record_activity(user=user,
                              service=u'HTTP',
                              field=u'Group',
                              action=u'Created')
        if data.get('membership_type'):
            group.membership_type = GroupMembershipType.from_string(
                data['membership_type'])
        if group.membership_type == GroupMembershipType.ldap:
            group.refresh_ldap_members()
        else:  # LDAP groups don't have any owners
            group.add_member(user, is_owner=True, agent=identity.current.user)
    response = jsonify(group.__json__())
    response.status_code = 201
    response.headers.add('Location', absolute_url(group.href))
    return response
Exemple #17
0
def extend_watchdog(recipe_id):
    """
    Extend the watchdog for a recipe.

    :param recipe_id: The id of the recipe.
    :jsonparam string kill_time: Time in seconds to extend the watchdog by.
    """
    data = read_json_request(request)
    return _extend_watchdog(recipe_id, data)
Exemple #18
0
def update_group(group_name):
    """
    Updates attributes of an existing group. The request body must be a JSON
    object containing one or more of the following keys.

    :jsonparam string group_name: New name for the group.
    :jsonparam string display_name: Display name of the group.
    :jsonparam string description: Description of the group.
    :jsonparam string root_password: Optional password. Can be an empty string.
      If empty, group jobs will use the root password preferences of the job submitter.
    :jsonparam string membership_type: New membership type for the group.
      See `POST /groups/` for more information.

    :status 200: Group was updated.
    :status 400: Invalid data was given.
    """
    group = _get_group_by_name(group_name)
    if not group.can_edit(identity.current.user):
        raise Forbidden403('Cannot edit group')
    data = read_json_request(request)
    with convert_internal_errors():
        user = identity.current.user
        renamed = False
        if 'group_name' in data:
            new_name = data['group_name']
            if new_name != group.group_name:
                if Group.query.filter(Group.group_name == new_name).count():
                    raise Conflict409('Group %s already exists' % new_name)
                group.set_name(user, u'HTTP', new_name)
                renamed = True
        if 'display_name' in data:
            new_display_name = data['display_name']
            if new_display_name != group.display_name:
                group.set_display_name(user, u'HTTP', new_display_name)
        if 'description' in data:
            new_description = data['description']
            if new_description != group.description:
                group.set_description(user, u'HTTP', new_description)
        if 'root_password' in data:
            new_root_password = data['root_password']
            if new_root_password != group.root_password:
                group.set_root_password(user, u'HTTP', new_root_password)
        # for backwards compatibility
        if data.pop('ldap', False):
            data['membership_type'] = 'ldap'
        if 'membership_type' in data:
            new_type = GroupMembershipType.from_string(
                    data['membership_type'])
            if (new_type == GroupMembershipType.ldap and not
                group.can_edit_ldap(user)):
                raise BadRequest400('Cannot edit LDAP group %s' % group)
            if new_type != group.membership_type:
                group.membership_type = new_type
    response = jsonify(group.to_json())
    if renamed:
        response.headers.add('Location', absolute_url(group.href))
    return response
Exemple #19
0
def login_oauth2():
    """
    Authenticates the current session using OAuth2.

    The caller may act as a proxy on behalf of another user by passing the
    *proxy_user* key. This requires that the caller has 'proxy_auth'
    permission.
    The request body must be a JSON object containing access_token.
    Proxy_user is optional.

    :jsonparam string access_token: The OAuth2 access token
    :jsonparam string proxy_user: Username on whose behalf the caller is proxying

    """

    payload = read_json_request(request)
    access_token = payload.get('access_token')
    proxy_user = payload.get('proxy_user')

    token_info_resp = requests.post(
        OAUTH2_TOKEN_INFO_URL,
        timeout=app.config.get('identity.soldapprovider.timeout'),
        data={
            'client_id': OAUTH2_CLIENT_ID,
            'client_secret': OAUTH2_CLIENT_SECRET,
            'token': access_token
        })
    token_info_resp.raise_for_status()
    token_info = token_info_resp.json()

    if not token_info['active']:
        raise Unauthorised401(u'Invalid token')

    if not 'https://beaker-project.org/oidc/scope' in token_info.get(
            'scope', '').split(' '):
        raise Unauthorised401(u'Token missing required scope')

    username = token_info.get('sub')
    if not username:
        raise Unauthorised401(u'Token missing subject')

    user = User.by_user_name(username)
    if user is None:
        raise Unauthorised401(u'Invalid username')
    if not user.can_log_in():
        raise Unauthorised401(u'Invalid username')
    if proxy_user:
        if not user.has_permission(u'proxy_auth'):
            raise Unauthorised401(u'%s does not have proxy_auth permission' %
                                  user.user_name)
        proxied_user = User.by_user_name(proxy_user)
        if proxied_user is None:
            raise Unauthorised401(u'Proxy user %s does not exist' % proxy_user)
        identity.set_authentication(proxied_user, proxied_by=user)
    else:
        identity.set_authentication(user)
    return jsonify({'username': user.user_name})
Exemple #20
0
def request_loan(fqdn):
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    message = (data.get('message') or u'').strip()
    requester = identity.current.user
    to = system.owner.email_address
    mail.system_loan_request(system, message, requester, to)
    # if we tracked loan requests we could return the details here
    return 'Requested', 201
Exemple #21
0
def update_reservation_request(id):
    """
    Updates the reservation request of a recipe. The request must be 
    :mimetype:`application/json`.

    :param id: Recipe's id.
    :jsonparam boolean reserve: Whether the system will be reserved at the end
      of the recipe. If true, the system will be reserved. If false, the system
      will not be reserved.
    :jsonparam int duration: Number of seconds to reserve the system.
    :jsonparam string when: Circumstances under which the system will be 
      reserved. Valid values are:

      onabort
        If the recipe status is Aborted.
      onfail
        If the recipe status is Aborted, or the result is Fail.
      onwarn
        If the recipe status is Aborted, or the result is Fail or Warn.
      always
        Unconditionally.
    """

    recipe = _get_recipe_by_id(id)
    if not recipe.can_update_reservation_request(identity.current.user):
        raise Forbidden403('Cannot update the reservation request of recipe %s'
                % recipe.id)
    data = read_json_request(request)
    if 'reserve' not in data:
        raise BadRequest400('No reserve specified')
    with convert_internal_errors():
        if data['reserve']:
            if not recipe.reservation_request:
                recipe.reservation_request = RecipeReservationRequest()
            if 'duration' in data:
                duration = int(data['duration'])
                if duration > MAX_SECONDS_PROVISION:
                    raise BadRequest400('Reservation time exceeds maximum time of %s hours'
                            % MAX_HOURS_PROVISION)
                old_duration = recipe.reservation_request.duration
                recipe.reservation_request.duration = duration
                _record_activity(recipe, u'Reservation Request', old_duration,
                        duration)
            if 'when' in data:
                old_condition = recipe.reservation_request.when
                new_condition = RecipeReservationCondition.from_string(data['when'])
                recipe.reservation_request.when = new_condition
                _record_activity(recipe, u'Reservation Condition',
                        old_condition, new_condition)
            session.flush() # to ensure the id is populated
            return jsonify(recipe.reservation_request.__json__())
        else:
            if recipe.reservation_request:
                session.delete(recipe.reservation_request)
                _record_activity(recipe, u'Reservation Request',
                        recipe.reservation_request.duration, None)
            return jsonify(RecipeReservationRequest.empty_json())
Exemple #22
0
def request_loan(fqdn):
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    message = (data.get('message') or u'').strip()
    requester = identity.current.user
    to = system.owner.email_address
    mail.system_loan_request(system, message, requester, to)
    # if we tracked loan requests we could return the details here
    return 'Requested', 201
Exemple #23
0
def update_group(group_name):
    """
    Updates attributes of an existing group. The request body must be a JSON
    object containing one or more of the following keys.

    :jsonparam string group_name: New name for the group.
    :jsonparam string display_name: Display name of the group.
    :jsonparam string description: Description of the group.
    :jsonparam string root_password: Optional password. Can be an empty string.
      If empty, group jobs will use the root password preferences of the job submitter.
    :jsonparam string membership_type: New membership type for the group.
      See `POST /groups/` for more information.

    :status 200: Group was updated.
    :status 400: Invalid data was given.
    """
    group = _get_group_by_name(group_name)
    if not group.can_edit(identity.current.user):
        raise Forbidden403('Cannot edit group')
    data = read_json_request(request)
    with convert_internal_errors():
        user = identity.current.user
        renamed = False
        if 'group_name' in data:
            new_name = data['group_name']
            if new_name != group.group_name:
                if Group.query.filter(Group.group_name == new_name).count():
                    raise Conflict409('Group %s already exists' % new_name)
                group.set_name(user, u'HTTP', new_name)
                renamed = True
        if 'display_name' in data:
            new_display_name = data['display_name']
            if new_display_name != group.display_name:
                group.set_display_name(user, u'HTTP', new_display_name)
        if 'description' in data:
            new_description = data['description']
            if new_description != group.description:
                group.set_description(user, u'HTTP', new_description)
        if 'root_password' in data:
            new_root_password = data['root_password']
            if new_root_password != group.root_password:
                group.set_root_password(user, u'HTTP', new_root_password)
        # for backwards compatibility
        if data.pop('ldap', False):
            data['membership_type'] = 'ldap'
        if 'membership_type' in data:
            new_type = GroupMembershipType.from_string(
                    data['membership_type'])
            if (new_type == GroupMembershipType.ldap and not
                group.can_edit_ldap(user)):
                raise BadRequest400('Cannot edit LDAP group %s' % group)
            if new_type != group.membership_type:
                group.membership_type = new_type
    response = jsonify(group.to_json())
    if renamed:
        response.headers.add('Location', absolute_url(group.href))
    return response
Exemple #24
0
def extend_watchdog(recipe_id):
    """
    Extend the watchdog for a recipe.

    :param recipe_id: The id of the recipe.
    :jsonparam string kill_time: Time in seconds to extend the watchdog by.
    """
    data = read_json_request(request)
    return _extend_watchdog(recipe_id, data)
Exemple #25
0
def login_krbv():
    """
    Authenticates the current session using Kerberos.
    The caller may act as a proxy on behalf of another user by passing the
    *proxy_user* key. This requires that the caller has 'proxy_auth'
    permission. The request body must be a JSON object containing krb_request.
    Proxy_user is optional.

    :jsonparam base64-encoded-string krb_request: KRB_AP_REQ message containing
        client credentials, as produced by :c:func:`krb5_mk_req`
    :jsonparam string proxy_user: Username on whose behalf the caller is proxying
    """
    import krbV
    import base64

    payload = read_json_request(request)
    krb_request = payload.get('krb_request')
    proxy_user = payload.get('proxy_user')

    context = krbV.default_context()
    server_principal = krbV.Principal(name=KRB_AUTH_PRINCIPAL, context=context)
    server_keytab = krbV.Keytab(name=KRB_AUTH_KEYTAB, context=context)

    auth_context = krbV.AuthContext(context=context)
    auth_context.flags = krbV.KRB5_AUTH_CONTEXT_DO_SEQUENCE | krbV.KRB5_AUTH_CONTEXT_DO_TIME
    auth_context.addrs = (socket.gethostbyaddr(request.remote_addr), 0,
                          request.remote_addr, 0)

    # decode and read the authentication request
    decoded_request = base64.decodestring(krb_request)
    auth_context, opts, server_principal, cache_credentials = context.rd_req(
        decoded_request,
        server=server_principal,
        keytab=server_keytab,
        auth_context=auth_context,
        options=krbV.AP_OPTS_MUTUAL_REQUIRED)
    cprinc = cache_credentials[2]

    # remove @REALM
    username = cprinc.name.split("@")[0]
    user = User.by_user_name(username)
    if user is None:
        raise Unauthorised401(u'Invalid username')
    if not user.can_log_in():
        raise Unauthorised401(u'Invalid username')
    if proxy_user:
        if not user.has_permission(u'proxy_auth'):
            raise Unauthorised401(u'%s does not have proxy_auth permission' %
                                  user.user_name)
        proxied_user = User.by_user_name(proxy_user)
        if proxied_user is None:
            raise Unauthorised401(u'Proxy user %s does not exist' % proxy_user)
        identity.set_authentication(proxied_user, proxied_by=user)
    else:
        identity.set_authentication(user)
    return jsonify({'username': user.user_name})
Exemple #26
0
def update_pool(pool_name):
    """
    Updates attributes of an existing system pool. The request body must be a JSON 
    object containing one or more of the following keys.

    :param pool_name: System pool's name.
    :jsonparam string name: New name for the system pool.
    :jsonparam string description: Description of the system pool.
    :jsonparam object owner: JSON object containing a ``user_name`` key or
      ``group_name`` key identifying the new owner for the system pool.
    :status 200: System pool was updated.
    :status 400: Invalid data was given.
    """
    pool = _get_pool_by_name(pool_name)
    if not pool.can_edit(identity.current.user):
        raise Forbidden403('Cannot edit system pool')
    data = read_json_request(request)

    # helper for recording activity below
    def record_activity(field, old, new, action=u'Changed'):
        pool.record_activity(user=identity.current.user,
                             service=u'HTTP',
                             action=action,
                             field=field,
                             old=old,
                             new=new)

    with convert_internal_errors():
        renamed = False
        if 'name' in data:
            new_name = data['name']
            if new_name != pool.name:
                if SystemPool.query.filter(
                        SystemPool.name == new_name).count():
                    raise Conflict409('System pool %s already exists' %
                                      new_name)
                record_activity(u'Name', pool.name, new_name)
                pool.name = new_name
                renamed = True
        if 'description' in data:
            new_description = data['description']
            if new_description != pool.description:
                record_activity(u'Description', pool.description,
                                new_description)
                pool.description = new_description
        if 'owner' in data:
            new_owner, owner_type = _get_owner(data['owner'])
            if owner_type == 'user':
                pool.change_owner(user=new_owner)
            else:
                pool.change_owner(group=new_owner)

    response = jsonify(pool.__json__())
    if renamed:
        response.headers.add('Location', absolute_url(pool.href))
    return response
Exemple #27
0
def create_group():
    """
    Creates a new user group in Beaker. The request must be 
    :mimetype:`application/json`.

    :jsonparam string group_name: Symbolic name for the group.
    :jsonparam string display_name: Human-friendly display name for the group.
    :jsonparam string description: Description of the group.
    :jsonparam string root_password: Optional root password for group jobs.
      If this is not set, group jobs will use the root password preferences of 
      the job submitter.
    :jsonparam string membership_type: Specifies how group membership is populated.
      Possible values are:

      * normal: Group is initially empty, members are explicitly added and removed by
        group owner.
      * ldap: Membership is populated from the LDAP group with the same group name.
      * inverted: Group contains all Beaker users *except* users who have been explicitly
        excluded by the group owner.

    :status 201: The group was successfully created.
    """
    user = identity.current.user
    data = read_json_request(request)
    if 'group_name' not in data:
        raise BadRequest400('Missing group_name key')
    if 'display_name' not in data:
        raise BadRequest400('Missing display_name key')
    # for backwards compatibility
    if data.pop('ldap', False):
        data['membership_type'] = 'ldap'
    try:
        Group.by_name(data['group_name'])
    except NoResultFound:
        pass
    else:
        raise Conflict409("Group '%s' already exists" % data['group_name'])
    with convert_internal_errors():
        group = Group.lazy_create(group_name=data['group_name'])
        group.display_name = data['display_name']
        group.description = data.get('description')
        group.root_password = data.get('root_password')
        session.add(group)
        group.record_activity(user=user, service=u'HTTP',
                field=u'Group', action=u'Created')
        if data.get('membership_type'):
            group.membership_type = GroupMembershipType.from_string(
                data['membership_type'])
        if group.membership_type == GroupMembershipType.ldap:
            group.refresh_ldap_members()
        else: # LDAP groups don't have any owners
            group.add_member(user, is_owner=True, agent=identity.current.user)
    response = jsonify(group.__json__())
    response.status_code = 201
    response.headers.add('Location', absolute_url(group.href))
    return response
Exemple #28
0
def report_problem(fqdn):
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    message = (data.get('message') or u'').strip()
    requester = identity.current.user
    mail.system_problem_report(system, message, reporter=requester)
    system.record_activity(user=requester, service=u'HTTP',
            action=u'Reported problem', field=u'Status', new=message)
    # if we tracked problem reports we could return the details here
    return 'Reported', 201
Exemple #29
0
def report_problem(fqdn):
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    message = (data.get('message') or u'').strip()
    requester = identity.current.user
    mail.system_problem_report(system, message, reporter=requester)
    system.record_activity(user=requester,
                           service=u'HTTP',
                           action=u'Reported problem',
                           field=u'Status',
                           new=message)
    # if we tracked problem reports we could return the details here
    return 'Reported', 201
Exemple #30
0
def grant_loan(fqdn):
    """
    Lends the system to the specified user (or borrows the system for
    the current user if no other user is specified)

    """
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    recipient = data.get("recipient")
    comment = data.get("comment")
    with convert_internal_errors():
        system.grant_loan(recipient, comment, service=u'HTTP')
    return jsonify(system.get_loan_details())
Exemple #31
0
def create_labcontroller():
    """
    Creates a new lab controller. The request must be :mimetype:`application/json`.

    :jsonparam string fqdn: Lab controller's new fully-qualified domain name.
    :jsonparam string user_name: User name associated with the lab controller.
    :jsonparam string email_address: Email of the user account associated with the lab controller.
    :jsonparam string password: Optional password for the user account used to login.
    :status 201: The lab controller was successfully created.
    :status 400: Invalid data was given.
    """
    data = read_json_request(request)
    return _create_labcontroller_helper(data)
Exemple #32
0
def create_labcontroller():
    """
    Creates a new lab controller. The request must be :mimetype:`application/json`.

    :jsonparam string fqdn: Lab controller's new fully-qualified domain name.
    :jsonparam string user_name: User name associated with the lab controller.
    :jsonparam string email_address: Email of the user account associated with the lab controller.
    :jsonparam string password: Optional password for the user account used to login.
    :status 201: The lab controller was successfully created.
    :status 400: Invalid data was given.
    """
    data = read_json_request(request)
    return _create_labcontroller_helper(data)
Exemple #33
0
def save_access_policy(pool_name):
    """
    Updates the access policy for a system pool.

    :param pool_name: System pool's name.
    :jsonparam array rules: List of rules to include in the new policy. This 
      replaces all existing rules in the policy. Each rule is a JSON object 
      with ``user``, ``group``, and ``everybody`` keys.
    """
    pool = _get_pool_by_name(pool_name)
    if not pool.can_edit_policy(identity.current.user):
        raise Forbidden403('Cannot edit system pool policy')
    data = read_json_request(request)
    _edit_access_policy_rules(pool, pool.access_policy, data['rules'])
    return jsonify(pool.access_policy.__json__())
Exemple #34
0
def save_access_policy(pool_name):
    """
    Updates the access policy for a system pool.

    :param pool_name: System pool's name.
    :jsonparam array rules: List of rules to include in the new policy. This 
      replaces all existing rules in the policy. Each rule is a JSON object 
      with ``user``, ``group``, and ``everybody`` keys.
    """
    pool = _get_pool_by_name(pool_name)
    if not pool.can_edit_policy(identity.current.user):
        raise Forbidden403('Cannot edit system pool policy')
    data = read_json_request(request)
    _edit_access_policy_rules(pool, pool.access_policy, data['rules'])
    return jsonify(pool.access_policy.__json__())
Exemple #35
0
def extend_watchdog_by_fqdn(fqdn):
    """
    Extend the watchdog for a recipe that is running on the system.

    :param fqdn: The system's fully-qualified domain name.
    :jsonparam string kill_time: Time in seconds to extend the watchdog by.
    """
    try:
        recipe = Recipe.query.join(Recipe.watchdog, Recipe.resource)\
            .filter(RecipeResource.fqdn == fqdn)\
            .filter(Recipe.status == TaskStatus.running).one()
    except NoResultFound:
        raise NotFound404('Cannot find any recipe running on %s' % fqdn)
    data = read_json_request(request)
    return _extend_watchdog(recipe.id, data)
Exemple #36
0
def extend_watchdog_by_fqdn(fqdn):
    """
    Extend the watchdog for a recipe that is running on the system.

    :param fqdn: The system's fully-qualified domain name.
    :jsonparam string kill_time: Time in seconds to extend the watchdog by.
    """
    try:
        recipe = Recipe.query.join(Recipe.watchdog, Recipe.resource)\
            .filter(RecipeResource.fqdn == fqdn)\
            .filter(Recipe.status == TaskStatus.running).one()
    except NoResultFound:
        raise NotFound404('Cannot find any recipe running on %s' % fqdn)
    data = read_json_request(request)
    return _extend_watchdog(recipe.id, data)
Exemple #37
0
def update_pool(pool_name):
    """
    Updates attributes of an existing system pool. The request body must be a JSON 
    object containing one or more of the following keys.

    :param pool_name: System pool's name.
    :jsonparam string name: New name for the system pool.
    :jsonparam string description: Description of the system pool.
    :jsonparam object owner: JSON object containing a ``user_name`` key or
      ``group_name`` key identifying the new owner for the system pool.
    :status 200: System pool was updated.
    :status 400: Invalid data was given.
    """
    pool = _get_pool_by_name(pool_name)
    if not pool.can_edit(identity.current.user):
        raise Forbidden403('Cannot edit system pool')
    data = read_json_request(request)
    # helper for recording activity below
    def record_activity(field, old, new, action=u'Changed'):
        pool.record_activity(user=identity.current.user, service=u'HTTP',
                action=action, field=field, old=old, new=new)
    with convert_internal_errors():
        renamed = False
        if 'name' in data:
            new_name = data['name']
            if new_name != pool.name:
                if SystemPool.query.filter(SystemPool.name == new_name).count():
                    raise Conflict409('System pool %s already exists' % new_name)
                record_activity(u'Name', pool.name, new_name)
                pool.name = new_name
                renamed = True
        if 'description' in data:
            new_description = data['description']
            if new_description != pool.description:
                record_activity(u'Description', pool.description, new_description)
                pool.description = new_description
        if 'owner' in data:
            new_owner, owner_type = _get_owner(data['owner'])
            if owner_type == 'user':
                pool.change_owner(user=new_owner)
            else:
                pool.change_owner(group=new_owner)

    response = jsonify(pool.__json__())
    if renamed:
        response.headers.add('Location', absolute_url(pool.href))
    return response
Exemple #38
0
def save_system_access_policy(fqdn):
    system = _get_system_by_FQDN(fqdn)
    if not system.can_edit_policy(identity.current.user):
        raise Forbidden403('Cannot edit system policy')
    if system.custom_access_policy:
        policy = system.custom_access_policy
    else:
        policy = system.custom_access_policy = SystemAccessPolicy()
    data = read_json_request(request)
    # Figure out what is added, what is removed.
    # Rules are immutable, so if it has an id it is unchanged,
    # if it has no id it is new.
    kept_rule_ids = frozenset(r['id'] for r in data['rules'] if 'id' in r)
    removed = []
    for old_rule in policy.rules:
        if old_rule.id not in kept_rule_ids:
            removed.append(old_rule)
    for old_rule in removed:
        system.record_activity(user=identity.current.user,
                               service=u'HTTP',
                               field=u'Access Policy Rule',
                               action=u'Removed',
                               old=repr(old_rule))
        policy.rules.remove(old_rule)
    for rule in data['rules']:
        if 'id' not in rule:
            if rule['user']:
                user = User.by_user_name(rule['user'])
                if user is None:
                    raise BadRequest400('No such user %r' % rule['user'])
            else:
                user = None
            try:
                group = Group.by_name(rule['group']) if rule['group'] else None
            except NoResultFound:
                raise BadRequest400('No such group %r' % rule['group'])
            permission = SystemPermission.from_string(rule['permission'])
            new_rule = policy.add_rule(user=user,
                                       group=group,
                                       everybody=rule['everybody'],
                                       permission=permission)
            system.record_activity(user=identity.current.user,
                                   service=u'HTTP',
                                   field=u'Access Policy Rule',
                                   action=u'Added',
                                   new=repr(new_rule))
    return jsonify(policy.__json__())
Exemple #39
0
def update_reservation(fqdn):
    """
    Updates the system's current reservation. The only permitted update is to 
    end the reservation (returning the system), and this is only permitted when 
    the reservation was "manual" (not made by the scheduler).
    """
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    # This interprets both PATCH and PUT as PATCH
    finish_time = data.get('finish_time')
    with convert_internal_errors():
        if finish_time == "now":
            reservation = system.unreserve_manually_reserved(
                service=u'HTTP', user=identity.current.user)
        else:
            raise ValueError('Reservation durations are not configurable')
    return jsonify(reservation.__json__())
Exemple #40
0
def update_loan(fqdn):
    """
    Updates a loan on the system with the given fully-qualified
    domain name.

    Currently, the only permitted update is to return it.
    """
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    # This interprets both PATCH and PUT as PATCH
    finish = data.get("finish")
    with convert_internal_errors():
        if finish == "now":
            system.return_loan(service=u'HTTP')
        else:
            raise ValueError("Loan durations are not yet configurable")
    return jsonify(system.get_loan_details())
Exemple #41
0
def update_reservation(fqdn):
    """
    Updates the system's current reservation. The only permitted update is to 
    end the reservation (returning the system), and this is only permitted when 
    the reservation was "manual" (not made by the scheduler).
    """
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    # This interprets both PATCH and PUT as PATCH
    finish_time = data.get('finish_time')
    with convert_internal_errors():
        if finish_time == "now":
            reservation = system.unreserve_manually_reserved(service=u'HTTP',
                    user=identity.current.user)
        else:
            raise ValueError('Reservation durations are not configurable')
    return jsonify(reservation.__json__())
Exemple #42
0
def update_loan(fqdn):
    """
    Updates a loan on the system with the given fully-qualified
    domain name.

    Currently, the only permitted update is to return it.
    """
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    # This interprets both PATCH and PUT as PATCH
    finish = data.get("finish")
    with convert_internal_errors():
        if finish == "now":
            system.return_loan(service=u'HTTP')
        else:
            raise ValueError("Loan durations are not yet configurable")
    return jsonify(system.get_loan_details())
Exemple #43
0
def update_recipeset(id):
    """
    Updates the attributes of a recipe set. The request must be 
    :mimetype:`application/json`.

    :param id: ID of the recipe set.
    :jsonparam string priority: Priority for the recipe set. Must be one of 
      'Low', 'Medium', 'Normal', 'High', or 'Urgent'. This can only be changed 
      while a recipe set is still queued. Job owners can generally only 
      *decrease* the priority of their recipe set, queue admins can increase 
      it.
    :jsonparam boolean waived: If true, the recipe set will be waived, regardless
      of its result.
    """
    recipeset = _get_rs_by_id(id)
    data = read_json_request(request)
    _update_recipeset(recipeset, data)
    return jsonify(recipeset.__json__())
Exemple #44
0
def create_powertype():
    """
    Creates a new power type. The request must be :mimetype:`application/json`.

    :jsonparam string name: Name for the power type.
    :status 201: The power type was successfully created.
    """
    data = read_json_request(request)
    with convert_internal_errors():
        if PowerType.query.filter_by(**data).count():
            raise Conflict409('Power type %s already exists' % data['name'])
        powertype = PowerType(**data)
        activity = Activity(identity.current.user, u'HTTP', u'Created', u'PowerType', powertype.name)
        session.add_all([powertype, activity])

    response = jsonify(powertype.__json__())
    response.status_code = 201
    return response
Exemple #45
0
def grant_loan(fqdn):
    """
    Lends the system to the specified user (or borrows the system for
    the current user if no other user is specified)

    """
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    recipient = data.get("recipient")
    if recipient is None:
        user_name = identity.current.user.user_name
    elif isinstance(recipient, basestring):
        user_name = recipient
    else:
        user_name = recipient.get('user_name')
    comment = data.get("comment")
    with convert_internal_errors():
        system.grant_loan(user_name, comment, service=u'HTTP')
    return jsonify(system.get_loan_details())
Exemple #46
0
def add_access_policy_rule(pool_name):
    """
    Adds a new rule to the access policy for a system pool. Each rule in the policy
    grants a permission to a single user, a group of users, or to everybody.

    See :ref:`system-access-policies-api` for a description of the expected JSON parameters.

    :param pool_name: System pool's name.
    """
    pool = _get_pool_by_name(pool_name)
    if not pool.can_edit_policy(identity.current.user):
        raise Forbidden403('Cannot edit system pool policy')
    policy = pool.access_policy
    rule = read_json_request(request)
    if rule.get('user', None):
        user = User.by_user_name(rule['user'])
        if not user:
            raise BadRequest400("User '%s' does not exist" % rule['user'])
    else:
        user = None

    if rule.get('group', None):
        try:
            group = Group.by_name(rule['group'])
        except NoResultFound:
            raise BadRequest400("Group '%s' does not exist" % rule['group'])
    else:
        group = None

    try:
        permission = SystemPermission.from_string(rule['permission'])
    except ValueError:
        raise BadRequest400('Invalid permission')
    new_rule = policy.add_rule(user=user,
                               group=group,
                               everybody=rule['everybody'],
                               permission=permission)
    pool.record_activity(user=identity.current.user,
                         service=u'HTTP',
                         field=u'Access Policy Rule',
                         action=u'Added',
                         new=repr(new_rule))
    return '', 204
Exemple #47
0
def update_reservation_request(id):
    """
    Updates the reservation request of a recipe. The request must be 
    :mimetype:`application/json`.

    :param id: Recipe's id.
    :jsonparam boolean reserve: Whether the system will be reserved at the end
      of the recipe. If true, the system will be reserved. If false, the system
      will not be reserved.
    :jsonparam int duration: Number of seconds to rerserve the system.
    """

    recipe = _get_recipe_by_id(id)
    if not recipe.can_update_reservation_request(identity.current.user):
        raise Forbidden403('Cannot update the reservation request of recipe %s'
                % recipe.id)
    data = read_json_request(request)
    if 'reserve' not in data:
        raise BadRequest400('No reserve specified')
    with convert_internal_errors():
        if data['reserve']:
            if 'duration' not in data:
                raise BadRequest400('No duration specified')
            duration = data['duration']
            if duration > MAX_SECONDS_PROVISION:
                raise BadRequest400('Reservation time exceeds maximum time of %s hours' % MAX_HOURS_PROVISION)
            if recipe.reservation_request:
                old_duration = recipe.reservation_request.duration
                recipe.reservation_request.duration = data['duration']
                _record_activity(recipe, u'Reservation Request', old_duration,
                        data['duration'])
            else:
                reservation_request = RecipeReservationRequest(data['duration'])
                recipe.reservation_request = reservation_request
                _record_activity(recipe, u'Reservation Request', None,
                        reservation_request.duration, 'Changed')
            return jsonify(recipe.reservation_request.__json__())
        else:
            if recipe.reservation_request:
                session.delete(recipe.reservation_request)
                _record_activity(recipe, u'Reservation Request',
                        recipe.reservation_request.duration, None)
            return jsonify(RecipeReservationRequest.empty_json())
Exemple #48
0
def post_recipeset_comment(id):
    """
    Adds a new comment to a recipe set. The request must be :mimetype:`application/json`.

    :param id: ID of the recipe set.
    :jsonparam string comment: Comment text.
    """
    recipeset = _get_rs_by_id(id)
    if not recipeset.can_comment(identity.current.user):
        raise Forbidden403('Cannot post recipe set comment')
    data = read_json_request(request)
    if 'comment' not in data:
        raise BadRequest400('Missing "comment" key')
    with convert_internal_errors():
        comment = RecipeSetComment(user=identity.current.user,
                                   comment=data['comment'])
        recipeset.comments.append(comment)
    session.flush()  # to populate the id
    return jsonify(comment.__json__())
Exemple #49
0
def grant_loan(fqdn):
    """
    Lends the system to the specified user (or borrows the system for
    the current user if no other user is specified)

    """
    system = _get_system_by_FQDN(fqdn)
    data = read_json_request(request)
    recipient = data.get("recipient")
    if recipient is None:
        user_name = identity.current.user.user_name
    elif isinstance(recipient, basestring):
        user_name = recipient
    else:
        user_name = recipient.get('user_name')
    comment = data.get("comment")
    with convert_internal_errors():
        system.grant_loan(user_name, comment, service=u'HTTP')
    return jsonify(system.get_loan_details())
Exemple #50
0
def update_reservation_request(id):
    """
    Updates the reservation request of a recipe. The request must be 
    :mimetype:`application/json`.

    :param id: Recipe's id.
    :jsonparam boolean reserve: Whether the system will be reserved at the end
      of the recipe. If true, the system will be reserved. If false, the system
      will not be reserved.
    :jsonparam int duration: Number of seconds to rerserve the system.
    """

    recipe = _get_recipe_by_id(id)
    if not recipe.can_update_reservation_request(identity.current.user):
        raise Forbidden403('Cannot update the reservation request of recipe %s'
                % recipe.id)
    data = read_json_request(request)
    if 'reserve' not in data:
        raise BadRequest400('No reserve specified')
    with convert_internal_errors():
        if data['reserve']:
            if 'duration' not in data:
                raise BadRequest400('No duration specified')
            duration = data['duration']
            if duration > MAX_SECONDS_PROVISION:
                raise BadRequest400('Reservation time exceeds maximum time of %s hours' % MAX_HOURS_PROVISION)
            if recipe.reservation_request:
                old_duration = recipe.reservation_request.duration
                recipe.reservation_request.duration = data['duration']
                _record_activity(recipe, u'Reservation Request', old_duration,
                        data['duration'])
            else:
                reservation_request = RecipeReservationRequest(data['duration'])
                recipe.reservation_request = reservation_request
                _record_activity(recipe, u'Reservation Request', None,
                        reservation_request.duration, 'Changed')
            return jsonify(recipe.reservation_request.__json__())
        else:
            if recipe.reservation_request:
                session.delete(recipe.reservation_request)
                _record_activity(recipe, u'Reservation Request',
                        recipe.reservation_request.duration, None)
            return jsonify(RecipeReservationRequest.empty_json())
Exemple #51
0
def create_powertype():
    """
    Creates a new power type. The request must be :mimetype:`application/json`.

    :jsonparam string name: Name for the power type.
    :status 201: The power type was successfully created.
    """
    data = read_json_request(request)
    with convert_internal_errors():
        if PowerType.query.filter_by(**data).count():
            raise Conflict409('Power type %s already exists' % data['name'])
        powertype = PowerType(**data)
        activity = Activity(identity.current.user, u'HTTP', u'Created',
                            u'PowerType', powertype.name)
        session.add_all([powertype, activity])

    response = jsonify(powertype.__json__())
    response.status_code = 201
    return response
Exemple #52
0
def add_system_to_pool(pool_name):
    """
    Add a system to a system pool

    :param pool_name: System pool's name.
    :jsonparam fqdn: System's fully-qualified domain name.

    """
    u = identity.current.user
    data = read_json_request(request)
    pool = _get_pool_by_name(pool_name, lockmode='update')
    if 'fqdn' not in data:
        raise BadRequest400('System FQDN not specified')
    try:
        system = System.by_fqdn(data['fqdn'], u)
    except NoResultFound:
        raise BadRequest400("System '%s' does not exist" % data['fqdn'])
    if not pool in system.pools:
        if pool.can_edit(u) and system.can_edit(u):
            system.record_activity(user=u,
                                   service=u'HTTP',
                                   action=u'Added',
                                   field=u'Pool',
                                   old=None,
                                   new=unicode(pool))
            system.pools.append(pool)
            system.date_modified = datetime.datetime.utcnow()
            pool.record_activity(user=u,
                                 service=u'HTTP',
                                 action=u'Added',
                                 field=u'System',
                                 old=None,
                                 new=unicode(system))
        else:
            if not pool.can_edit(u):
                raise Forbidden403('You do not have permission to '
                                   'add systems to pool %s' % pool.name)
            if not system.can_edit(u):
                raise Forbidden403('You do not have permission to '
                                   'modify system %s' % system.fqdn)

    return '', 204
Exemple #53
0
def add_access_policy_rule(pool_name):
    """
    Adds a new rule to the access policy for a system pool. Each rule in the policy
    grants a permission to a single user, a group of users, or to everybody.

    See :ref:`system-access-policies-api` for a description of the expected JSON parameters.

    :param pool_name: System pool's name.
    """
    pool = _get_pool_by_name(pool_name)
    if not pool.can_edit_policy(identity.current.user):
        raise Forbidden403('Cannot edit system pool policy')
    policy = pool.access_policy
    rule = read_json_request(request)
    if rule.get('user', None):
        user = User.by_user_name(rule['user'])
        if not user:
            raise BadRequest400("User '%s' does not exist" % rule['user'])
        if user.removed:
            raise BadRequest400('Cannot add deleted user %s to access policy' % user.user_name)
    else:
        user = None

    if rule.get('group', None):
        try:
            group = Group.by_name(rule['group'])
        except NoResultFound:
            raise BadRequest400("Group '%s' does not exist" % rule['group'])
    else:
        group = None

    try:
        permission = SystemPermission.from_string(rule['permission'])
    except ValueError:
        raise BadRequest400('Invalid permission')
    new_rule = policy.add_rule(user=user, group=group,
                               everybody=rule['everybody'],
                               permission=permission)
    pool.record_activity(user=identity.current.user, service=u'HTTP',
                         field=u'Access Policy Rule', action=u'Added',
                         new=repr(new_rule))
    return '', 204
Exemple #54
0
def add_permission(group_name):
    """
    Add a permission to a group.

    :param group_name: Group's name.
    :jsonparam permission_name: Permission's name.

    """
    u = identity.current.user
    data = read_json_request(request)
    group = _get_group_by_name(group_name, lockmode='update')
    if 'permission_name' not in data:
        raise BadRequest400('Permission name not specified')
    permission = _get_permission_by_name(data['permission_name'])
    if permission not in group.permissions:
        group.permissions.append(permission)
        group.record_activity(user=u, service=u'HTTP',
                             action=u'Added', field=u'Permission', old=None,
                             new=unicode(permission))
    return '', 204
Exemple #55
0
def post_recipe_task_comment(recipeid, taskid):
    """
    Adds a new comment to a recipe task. The request must be :mimetype:`application/json`.

    :param recipeid: Recipe id.
    :param taskid: Recipe task id.
    :jsonparam string comment: Comment text.
    """
    task = _get_recipe_task_by_id(recipeid, taskid)
    if not task.can_comment(identity.current.user):
        raise Forbidden403('Cannot post recipe task comment')
    data = read_json_request(request)
    if 'comment' not in data:
        raise BadRequest400('Missing "comment" key')
    with convert_internal_errors():
        comment = RecipeTaskComment(user=identity.current.user,
                comment=data['comment'])
        task.comments.append(comment)
    session.flush() # to populate the id
    return jsonify(comment.__json__())
Exemple #56
0
def create_user():
    """
    Creates a new user account in Beaker.
    """
    data = read_json_request(request)
    with convert_internal_errors():
        new_user_name = data.get('user_name', '').strip()
        existing_user = User.by_user_name(new_user_name)
        if existing_user is not None:
            raise Conflict409('User %s already exists' % new_user_name)
        new_display_name = data.get('display_name', '').strip()
        new_email_address = data.get('email_address', '').strip()
        user = User(user_name=new_user_name,
                    display_name=new_display_name,
                    email_address=new_email_address)
        session.add(user)
        session.flush() # to populate id
    response = jsonify(user_full_json(user))
    response.status_code = 201
    response.headers.add('Location', absolute_url(user.href))
    return response
Exemple #57
0
def _create_keystone_trust(user):
    if not config.get('openstack.identity_api_url'):
        raise BadRequest400("OpenStack Integration is not enabled")
    if not user.can_edit_keystone_trust(identity.current.user):
        raise Forbidden403('Cannot edit Keystone trust of user %s' % user.username)
    data = read_json_request(request)
    if 'openstack_username' not in data:
        raise BadRequest400('No OpenStack username specified')
    if 'openstack_password' not in data:
        raise BadRequest400('No OpenStack password specified')
    if 'openstack_project_name' not in data:
        raise BadRequest400('No OpenStack project name specified')
    try:
        trust_id = dynamic_virt.create_keystone_trust(data['openstack_username'],
                data['openstack_password'], data['openstack_project_name'])
    except ValueError as err:
        raise BadRequest400(u'Could not authenticate with OpenStack using your credentials: %s' % unicode(err))
    user.openstack_trust_id = trust_id
    user.record_activity(user=identity.current.user, service=u'HTTP',
            field=u'OpenStack Trust ID', action=u'Changed')
    return jsonify({'openstack_trust_id': trust_id})
Exemple #58
0
def add_submission_delegate(username):
    """
    Adds a submission delegate for a user account. Submission delegates are 
    other users who are allowed to submit jobs on behalf of this user.

    :param username: The user's username.
    :jsonparam string user_name: The submission delegate's username.
    """
    user = _get_user(username)
    if not user.can_edit(identity.current.user):
        raise Forbidden403('Cannot edit user %s' % user)
    data = read_json_request(request)
    if 'user_name' not in data:
        raise BadRequest400('Missing "user_name" key to specify submission delegate')
    submission_delegate = User.by_user_name(data['user_name'])
    if submission_delegate is None:
        raise BadRequest400('Submission delegate %s does not exist' % data['user_name'])
    try:
        user.add_submission_delegate(submission_delegate, service=u'HTTP')
    except NoChangeException as e:
        raise Conflict409(unicode(e))
    return 'Added', 201
Exemple #59
0
def grant_ownership(group_name):
    """
    Grant group ownership to a user. The user can either be the group member or
    not. If the user is not the group member, it will be added first.

    :param group_name: Group's name.
    :jsonparam string user_name: User's username.

    """
    u = identity.current.user
    data = read_json_request(request)
    group = _get_group_by_name(group_name, lockmode='update')
    if not group.can_modify_ownership(identity.current.user):
        raise Forbidden403('Cannot edit ownership of group %s' % group_name)
    if 'user_name' not in data:
        raise BadRequest400('User not specified')
    user_name = data['user_name']
    user = _get_user_by_username(user_name)
    if not group.has_owner(user):
        group.grant_ownership(user, agent=identity.current.user)
    else:
        raise Conflict409('User %s is already an owner of group %s' % (user.user_name, group_name))
    return '', 204
Exemple #60
0
def extend_watchdog_by_taskspec(taskspec):
    """
    Extend the watchdog for a recipe identified by a taskspec. The valid type
    of a taskspec is either R(recipe) or T(recipe-task).
    See :ref:`Specifying tasks <taskspec>` in :manpage:`bkr(1)`.

    :param taskspec: A taskspec argument that identifies a recipe or recipe task.
    :jsonparam string kill_time: Time in seconds to extend the watchdog by.
    """
    if not taskspec.startswith(('R', 'T')):
        raise BadRequest400('Taskspec type must be one of [R, T]')

    try:
        obj = TaskBase.get_by_t_id(taskspec)
    except BeakerException as exc:
        raise NotFound404(unicode(exc))

    if isinstance(obj, Recipe):
        recipe = obj
    else:
        recipe = obj.recipe
    data = read_json_request(request)
    return _extend_watchdog(recipe.id, data)